From 5f951ae560f9b27399fea2f0a4831527e1720013 Mon Sep 17 00:00:00 2001 From: Pegmode Date: Mon, 23 Nov 2020 04:05:41 -0700 Subject: [PATCH] initial commit --- .gitignore | 2 + LSDJ2DMW.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++ LSDJ2DMW.h | 79 +++++++++++++++++++++++++++ LSDJDecompress.c | 131 ++++++++++++++++++++++++++++++++++++++++++++ LSDJDecompress.h | 27 ++++++++++ Makefile | 7 +++ debug.c | 39 ++++++++++++++ debug.h | 0 utils.c | 127 +++++++++++++++++++++++++++++++++++++++++++ utils.h | 5 ++ 10 files changed, 555 insertions(+) create mode 100644 .gitignore create mode 100644 LSDJ2DMW.c create mode 100644 LSDJ2DMW.h create mode 100644 LSDJDecompress.c create mode 100644 LSDJDecompress.h create mode 100644 Makefile create mode 100644 debug.c create mode 100644 debug.h create mode 100644 utils.c create mode 100644 utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..006a4a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +outputwaves/ +*.exe diff --git a/LSDJ2DMW.c b/LSDJ2DMW.c new file mode 100644 index 0000000..3c10d50 --- /dev/null +++ b/LSDJ2DMW.c @@ -0,0 +1,138 @@ +#include "LSDJ2DMW.h" + +/* +instrument names (1e7a-1fb9) + names are 5 bytes long + + +pay attention to wave instrument param length!!! + indexed from 0 +*/ + + + +int main(int argc, char* argv[]){ + if(argc <= 1){ + printf(HELPSTRING); + exit(0); + } + if(strcmp("-h",argv[1]) == 0){ + printf(HELPSTRING); + exit(0); + } + if(strcmp(argv[1],"") == 0){ + printf("ERROR: input filename not entered\n"); + printf(HELPSTRING); + exit(1); + } + if(access(argv[1], F_OK ) == -1){ + printf("ERROR: file '%s' does not exist\n",argv[1]); + printf(HELPSTRING); + exit(1); + } + strcpy(GLOBALINFILE,argv[1]); + int runcode = 0; + for(int i = 2; i < argc; i++){ + if(strcmp("-p",argv[i]) == 0){//print ripped waves and all songs + GLOBALWAVEPRINTFLAG = 1; + GLOBALFILEPRINTFLAG = 1; + } + else if(strcmp("-pw",argv[i]) == 0){//print ripped waves + GLOBALWAVEPRINTFLAG = 1; + } + else if(strcmp("-pf",argv[i]) == 0){//print all songs + GLOBALFILEPRINTFLAG = 1; + } + + else if(strcmp("-l",argv[i]) == 0){//rip all wavetables in loaded song + runcode = 5; + } + else if(strcmp("-lw",argv[i]) == 0){//rip single wave in loaded song + GLOBALRIPWAVENUM = atoi(argv[++i]); + runcode = 6; + } + else if(strcmp("-lwr",argv[i]) == 0){//rip range of wavetables from loaded song + GLOBALRIPWAVENUM = atoi(argv[++i]); + GLOBALENDWAVENUM = atoi(argv[++i]); + if (GLOBALRIPWAVENUM>GLOBALENDWAVENUM){ + printf("ERROR: first value in range cannot be larger than the second value in range\n"); + exit(1); + } + runcode = 7; + } + + else if(strcmp("-a",argv[i]) == 0){//rip all waves in all songs + runcode = 4; + } + else if(strcmp("-s",argv[i]) == 0){//rip single song + strcpy(GLOBALSONGNAME,argv[++i]); + runcode = 1; + } + else if(strcmp("-sw",argv[i]) == 0){//rip single wave in song + strcpy(GLOBALSONGNAME,argv[++i]); + GLOBALRIPWAVENUM = atoi(argv[++i]); + //GLOBALRIPWAVENUM = argv[++i]; + runcode = 2; + } + else if(strcmp("-swr",argv[i]) == 0){//rip range of waves in song + strcpy(GLOBALSONGNAME,argv[++i]); + GLOBALRIPWAVENUM = atoi(argv[++i]); + GLOBALENDWAVENUM = atoi(argv[++i]); + if (GLOBALRIPWAVENUM>GLOBALENDWAVENUM){ + printf("ERROR: first value in range cannot be larger than the second value in range\n"); + exit(1); + } + //GLOBALRIPWAVENUM = argv[++i]; + //GLOBALENDWAVENUM = argv[++i]; + runcode = 3; + } + else if(strcmp("-x",argv[i]) == 0){ + GLOBALOUTPUTINDEXMODE = 1; + } + else if(strcmp("-nw",argv[i]) == 0){ + runcode = 10; + } + else if(strcmp("-h",argv[i]) == 0){ + printf(HELPSTRING); + exit(0); + } + else if(strcmp("-d",argv[i]) == 0){ + test(); + exit(0); + } + } + switch (runcode){ + case 0:// rip all wavetables in loaded song(default) + ripWaveRangeFromLoaded(GLOBALINFILE,0,255); + break; + case 1://single file + ripSingleSong(GLOBALINFILE,GLOBALSONGNAME); + break; + case 2://single wavetable in single song + ripWavetableRangeFromSong(GLOBALINFILE,GLOBALSONGNAME,GLOBALRIPWAVENUM,GLOBALRIPWAVENUM); + break; + case 3://rip range of wavetables in single song + ripWavetableRangeFromSong(GLOBALINFILE,GLOBALSONGNAME,GLOBALRIPWAVENUM,GLOBALENDWAVENUM); + break; + case 4://rip all in all songs + ripAllWavetables(GLOBALINFILE); + break; + case 5://rip all wavetables in loaded song + ripWaveRangeFromLoaded(GLOBALINFILE,0,255); + break; + case 6://rip single wavetable in loaded song + ripWaveRangeFromLoaded(GLOBALINFILE,GLOBALRIPWAVENUM,GLOBALRIPWAVENUM); + break; + case 7://rip wavetables in given range of song + ripWaveRangeFromLoaded(GLOBALINFILE,GLOBALRIPWAVENUM,GLOBALENDWAVENUM); + break; + case 10://do nothing -nw + break; + default://something went wrong + break; + } + if(GLOBALFILEPRINTFLAG){ + printAllFilenames("test1.sav"); + } +} + diff --git a/LSDJ2DMW.h b/LSDJ2DMW.h new file mode 100644 index 0000000..84aa8d4 --- /dev/null +++ b/LSDJ2DMW.h @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include + +#ifndef SYSTEMOS + #define SYSTEMOS 0//windows +#endif +int GLOBALWAVEPRINTFLAG = 0;//print waves +int GLOBALFILEPRINTFLAG = 0;//print scanned files +int GLOBALOUTFILEFLAG = 1;//output .dmw's + +#if SYSTEMOS == 0//windows + char* GLOBALFILEDIRECTORY = ".\\outputwaves\\";//directory to dump files into (windows) +#endif +#if SYSTEMOS == 1//linux + char* GLOBALFILEDIRECTORY = "./outputwaves/";//directory to dump files into (linux) +#endif +char GLOBALINFILE[50] = ""; +char GLOBALSONGNAME[50] = ""; +int GLOBALRIPWAVENUM = 0;//lazy solution +int GLOBALENDWAVENUM = 0; +int GLOBALOUTPUTINDEXMODE = 0;//1 = dec, 2 = hex +#define GBWAVESIZE 32 +#define GBBITDEPTH 4 +#define LSDJWAVESTARTADDRESS 0x6000 +#define LSDJWAVEENDADDRESS 0x6FFF +#define LSDJVERSIONADDRESS 0x7FFF +#define FILENAMES_BASEADDRESS 0x8000 +#define FILENAME_LENGTH 8 + +char* HELPSTRING = \ +"==LSDJ2DMW 1.0==\n\ +by Pegmode (Dan Chu)\n\n\ +Rips wavetables from LSDJ into .dmw for use in Deflemask\n\ +USAGE: LSDJ2SDMW [options]\n\ +OPTIONS: \n\ +-l: rip all wavetables in loaded module (default)\n\ +-lw : rips a single wavetable from the loaded module\n\ +-lwr : rip a range of wavetables from the loaded module\n\ +-a: rips all wavetables from all saved songs\n\ +-s: : rip all wavetables from a single song\n\ +-sw: : rips a specific wavetable from a given song (0-255)\n\ +-swr: : rips wavetables in a given range in a specific song (0-255)\n\ +-p: print ripped waves and files\n\ +-pw: print ripped waves\n\ +-pf: print songs\n\ +-x: format outputted wavetable indices as hex\n\ +-nw: don't write files or parse files. Useful in combination with -p/-pf for reading songnames\n\ +\n\ +Example usage: LSDJ2SFMW test.sav -p -lwr 0 5\n\ +default behavior rips all wavetables in loaded song \n\ +for all behavior involving the 'loaded song', the 'loaded song' means what you have loaded in LSDj before saving\n\ +by default waves are dumped into directory ./outputwaves/\n\ +LSDJ versions less than 6.7.0 are not guaranteed to function properly with this program\n\ +"; + +#include "LSDJDecompress.c" +#include "utils.c" +#include "ripTools.c" +#include "debug.c" + + + +/* +Version Codes + * 0: < 3.6.0 + * 1: 3.6.0 + * 2: 3.6.1 + * 3: 4.4.0 + * 4: 5.7.0 + * 5: 6.3.0 + * 6: 6.7.0 + * 7: 6.8.0 + * 8: 7.1.0 + * 9: 7.5.0 + * 10: 7.9.8 +*/ diff --git a/LSDJDecompress.c b/LSDJDecompress.c new file mode 100644 index 0000000..e207042 --- /dev/null +++ b/LSDJDecompress.c @@ -0,0 +1,131 @@ +#include "LSDJDecompress.h" + +//info https://littlesounddj.fandom.com/wiki/File_Management_Structure +//https://littlesounddj.fandom.com/wiki/.sav_structure +//WARNING: If comparing outputted binaries to .sav, timers may be different + uint8_t defaultInstrument[] = {0xa8,0x0,0x0,0xff,0x0,0x0,0x3,0x0,0x0,0xd0,0x0,0x0,0x0,0xf3,0x0,0x0};//length 16 + uint8_t defaultWave[] = {0x8e,0xcd,0xcc,0xbb,0xaa,0xa9,0x99,0x88,0x87,0x76,0x66,0x55,0x54,0x43,0x32,0x31};//length 16 + + +int calculateBlockBase(int blockNumber){//base address in .sav memory map + return (0x8000 + 0x200 * blockNumber); +} + +int findFirstBlockFromFileIndex(int fileIndex ,char* filePath){ + FILE* f = fopen(filePath,"rb"); + uint8_t fileAllocationTable[FILE_ALLOC_SIZE] = {0xff}; + fseek(f,FILE_ALLOC_ADDRESSBASE,SEEK_SET); + fread(fileAllocationTable,FILE_ALLOC_SIZE,1,f); + fclose(f); + + for(int i = 0; i < FILE_ALLOC_SIZE; i++){//search filealloc table for file index + //block indexed from 1 + //printf("%i\n",fileAllocationTable[i]); + if(fileAllocationTable[i] == fileIndex){ + return i+1; + } + } + return -1; +} + +int calculateRealAddress(int cPos){ + return 0x8200 + cPos; +} + +void createFileBlocksBuffer(char* filePath, uint8_t* blockBuffer){ + FILE* f = fopen(filePath,"rb"); + fseek(f,BLOCK_BASE_ADDRESS,SEEK_SET); + fread(blockBuffer,FILE_BLOCKS_SIZE,1,f); + fclose(f); + + FILE* fz = fopen("debugout2.bin","wb"); + fwrite(blockBuffer,1,FILE_BLOCKS_SIZE,fz); + fclose(fz); +} +int uncompressDefault(uint8_t* uncompressedFile,uint8_t* defaultContent,int uPos, int cPos, uint8_t* compressedFile){ + int loopLength = compressedFile[cPos]; + for(int i = 0; i < loopLength; i++){ + memcpy(&uncompressedFile[uPos],defaultContent,DEFAULT_DECOMPRESS_LENGTH); + uPos += 16; + } + return uPos; +} + +int calculateBlockOffsetFromBlockIndex(int blockIndex){//base offset + return (blockIndex-1) * 0x200; +} + + +void decompressBlocks(int startBlockIndex,char* filePath,uint8_t* uncompressedFile){ + uint8_t compressedFile[FILE_BLOCKS_SIZE] = {0}; + createFileBlocksBuffer(filePath,compressedFile); + int uPos = 0;//uncompressed position + int cPos = calculateBlockOffsetFromBlockIndex(startBlockIndex);//compressed positon + for(;uPos < 0x8000;){ + //printf("%d\n",uPos ); + switch(compressedFile[cPos]){ + case 0xC0://RLE?s + cPos++; + if(compressedFile[cPos] != 0xC0){//IS RLE + uint8_t byteValue = compressedFile[cPos++]; + uint8_t rleLength = compressedFile[cPos++]; + for(int j = 0; j < (int)rleLength; j++){ + //compressedFile[uPos++] = byteValue; + uncompressedFile[uPos++] = byteValue; + } + //printf("RLE val: %X len: %d c adr: %X u adr: %X\n",byteValue,rleLength,calculateRealAddress(cPos - 3),uPos); + } + else{//IS NOT RLE + uncompressedFile[uPos++] = 0xC0; + cPos++; + } + break; + case 0xE0://command + cPos++; + switch (compressedFile[cPos]){ + case 0xE0://not a command + uncompressedFile[uPos++] = 0xE0; + cPos++; + break; + case 0xF1://default instrument + //printf("\nPasting Default instrument c adr:%X u adr:%X\n",calculateRealAddress(cPos),uPos); + cPos++; + uPos = uncompressDefault(uncompressedFile,defaultInstrument,uPos,cPos,compressedFile); + cPos++; + break; + case 0xF0://default wavetable + //printf("\nPasting Default WT c adr:%X u adr:%X\n",calculateRealAddress(cPos),uPos); + cPos++; + uPos = uncompressDefault(uncompressedFile,defaultWave,uPos,cPos,compressedFile); + cPos++; + break; + case 0xFF://end of file + //printf("\nFinished Decompression c adr:%X u adr:%X\n",calculateRealAddress(cPos),uPos); + return; + break; + default://block Switch + //printf("\nSwitching Blocks c adr:%X u adr:%X\n",calculateRealAddress(cPos),uPos); + cPos = calculateBlockOffsetFromBlockIndex(compressedFile[cPos]); + //printf(" new c adr:%X\n",calculateRealAddress(cPos)); + //printf("next: %X\n",compressedFile[cPos]); + break; + } + + break; + default://write byte + uncompressedFile[uPos++] = compressedFile[cPos++]; + break; + } + } +} + +void decompressLSDJFile(int fileIndex,char* fileName, uint8_t* uncompressedFile){ + int blockIndex = findFirstBlockFromFileIndex(fileIndex,fileName); + //printf("BLOCK INDEX: %i\n",blockIndex); + if(blockIndex == -1){ + //BLOCK INDEX NOT FOUND + printf("Block Index not found\n"); + } + else decompressBlocks(blockIndex,fileName,uncompressedFile); +} + diff --git a/LSDJDecompress.h b/LSDJDecompress.h new file mode 100644 index 0000000..bb18ab7 --- /dev/null +++ b/LSDJDecompress.h @@ -0,0 +1,27 @@ +#include +#include +#include + +#define FILE_ALLOC_ADDRESSBASE 0x8141 +#define FILE_ALLOC_SIZE 191 +#define FILE_BLOCK_LENGTH 0x200 +#define DEFAULT_DECOMPRESS_LENGTH 16 +#define MAX_FILE_NUMBER 0x20 +#define BLOCK_BASE_ADDRESS 0x8200 +#define FILE_BLOCKS_SIZE 0x17DFF + + +/* + * 0: < 3.6.0 + * 1: 3.6.0 + * 2: 3.6.1 + * 3: 4.4.0 + * 4: 5.7.0 + * 5: 6.3.0 + * 6: 6.7.0 + * 7: 6.8.0 + * 8: 7.1.0 + * 9: 7.5.0 + * 10: 7.9.8 + * C: 8.7.7? +*/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4c76f87 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +C = gcc +outname = LSDJ2DMW +dependencies = LSDJ2DMW.c LSDJ2DMW.h utils.c utils.h ripTools.c ripTools.h LSDJDecompress.c LSDJDecompress.h debug.c debug.h +windows: $(dependencies) + $(C) LSDJ2DMW.c -o $(outname).exe +linux: $(dependencies) #untested + $(C) LSDJ2DMW.c -o $(outname) -D SYSTEMOS = 1 \ No newline at end of file diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..f5df95b --- /dev/null +++ b/debug.c @@ -0,0 +1,39 @@ +#include "debug.h" + + +void decompressTest(){ + uint8_t lsdjFile[0x8000] = {0}; + int fileIndex = 2; + decompressLSDJFile(fileIndex,"test1.sav",lsdjFile); + + //write test + FILE* fo = fopen("lsdjFileOut.bin","wb"); + fwrite(lsdjFile,0x8000,1,fo); + fclose(fo); +} + + +void test(){ + //decompressTest(); + ripAllWavetables("test1.sav"); + //uint8_t lsdjFile[0x8000]; + //decompressLSDJFile(2,"test1.sav",lsdjFile); + printAllFilenames("test1.sav"); + /* + uint8_t** wavetables; + int wtLength = 3; + wavetables = allocWavetableArray(wtLength); + + uint8_t lsdjFile[0x8000] = {0}; + int fileIndex = 0; + decompressLSDJFile(fileIndex,"test1.sav",lsdjFile); + + readWaveFromIndexLSDJ(lsdjFile,0,wavetables[1]); + + //memcpy(wavetables[1],wavebuffer,32); + //printWave(wavebuffer); + printWave(wavetables[0]); + printWave(wavetables[1]); + free(wavetables); + */ +} \ No newline at end of file diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..e69de29 diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..a50662e --- /dev/null +++ b/utils.c @@ -0,0 +1,127 @@ +#include "utils.h" + +void printWave(uint8_t* wave){ + printf("Wave: "); + for(int i = 0; i < GBWAVESIZE; i++){ + printf("%X ",wave[i]); + } + printf("\n"); +} + +uint8_t getLSDJVersion(char* filepath){ + FILE* f = fopen(filepath, "rb"); + uint8_t version; + fseek(f,LSDJVERSIONADDRESS,SEEK_SET); + fread(&version,1,sizeof(uint8_t),f); + fclose(f); + return version; +} + +void wavetableTo4bit(uint8_t* inputWave,uint8_t* outputWave){//pad from 8 bit to 4 bit + for(int i = 0; i < 16;i++){ + //low nibble + outputWave[i*2+1] = 0xF&inputWave[i]; + //high nibble + outputWave[i*2] = (0xF0&inputWave[i])>>4; + } +} + +int findFileindexFromName(char* filepath, char* filename){ + //Files are indexed from 0 + char songNames[256];// + FILE* f = fopen(filepath,"rb"); + fseek(f,FILENAMES_BASEADDRESS,SEEK_SET); + fread(songNames,256,1,f); + fclose(f); + for(int i = 0; i < 20; i++){ + if(memcmp(filename,&songNames[i*8],8) == 0){ + return i; + } + } + return -1; +} + +void findNameFromFileindex(char* filepath, char* filename, int index){ + //filename string is 9 long w/ null term + //Files are indexed from 0 + char songNames[256];// + FILE* f = fopen(filepath,"rb"); + fseek(f,FILENAMES_BASEADDRESS,SEEK_SET); + fread(songNames,256,1,f); + fclose(f); + + memcpy(filename,&songNames[8*index],8); + songNames[9] = '\0'; + +} + +void readWaveFromIndexFILERAW(char* fileName,int waveNumber,uint8_t* wave){//reads a single wave + if(waveNumber >= 256){ + printf("Warning: waveNumber %d is out of the indexable range (0-255)\n",waveNumber); + return; + } + uint8_t waveBuffer[GBWAVESIZE/2]; + int waveOffset = (waveNumber*16)+LSDJWAVESTARTADDRESS; + FILE* f = fopen(fileName,"rb"); + fseek(f,waveOffset,SEEK_SET); + fread(waveBuffer,GBWAVESIZE/2,sizeof(uint8_t),f); + fclose(f); + wavetableTo4bit(waveBuffer,wave); + +} + +void readWaveFromIndexLSDJ(uint8_t* lsdjFile, int waveNumber, uint8_t* wave){ + if(waveNumber >= 256){ + printf("Warning: waveNumber %d is out of the indexable range (0-255)\n",waveNumber); + return; + } + uint8_t waveBuffer[GBWAVESIZE/2]; + int waveOffset = (waveNumber*16)+LSDJWAVESTARTADDRESS; + memcpy(waveBuffer,&lsdjFile[waveOffset],GBWAVESIZE/2); + wavetableTo4bit(waveBuffer,wave); + +} + +int getLoadedSongindex(char* filename){ + uint8_t songIndex; + FILE* f = fopen(filename,"rb"); + fseek(f,0x8140,SEEK_SET);//active file + fread(&songIndex,1,1,f); + fclose(f); + return songIndex; +} + +void writeDMW(uint8_t* wavetable,char* filepath){ + char outfilepath[60]; + sprintf(outfilepath,"%s.dmw",filepath); + uint8_t header[4] = {GBWAVESIZE}; + FILE* f; + f = fopen(outfilepath,"wb"); + fwrite(header,4,sizeof(uint8_t),f); + fputc(GBBITDEPTH,f); + for(int i = 0; i < GBWAVESIZE; i++){ + fputc(wavetable[i],f); + } + fclose(f); +} + +uint8_t** allocWavetableArray(int numWT){ + int size = 32 * numWT; + uint8_t** waveTables; + uint8_t* ptrBuffer; + waveTables = (uint8_t**)malloc(size); + //setup 2D + ptrBuffer = (uint8_t*)(waveTables + numWT); + for(int i = 0; i < numWT; i++){ + waveTables[i] = (ptrBuffer + 32 * i); + } + return waveTables; +} + +void printAllFilenames(char* filepath){ + char strr[9]; + for(int i = 0; i < 20; i++){ + findNameFromFileindex(filepath,strr,i); + printf("file%d: %s\n",i+1,strr); + } +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..fddbb9c --- /dev/null +++ b/utils.h @@ -0,0 +1,5 @@ +#include +#include +#include +#include +#include \ No newline at end of file