├── CMakeLists.txt ├── CompileMLKTool.c ├── CompileWLKTool.c ├── DIMUnpack.c ├── DiamondRushExtract.c ├── FoxRangerExtract.c ├── LBXUnpack.c ├── LICENSE ├── README.md ├── SSF2_Compr.txt ├── danbidec.c ├── gensqu_dec.c ├── kenji_dec.c ├── lzss-lib.c ├── lzss-lib.h ├── lzss-tool.c ├── mrndec.c ├── piyo_dec.c ├── rekiai_dec.c ├── stdtype.h ├── wolfteam_dec.c ├── x68k_sps_dec.c └── xordec.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.2) 2 | project (tools LANGUAGES C CXX) 3 | 4 | 5 | if(MSVC) 6 | if(NOT MSVC_VERSION LESS 1400) 7 | add_definitions("-D _CRT_SECURE_NO_WARNINGS") 8 | endif() 9 | endif() 10 | if(CMAKE_COMPILER_IS_GNUCC) 11 | endif(CMAKE_COMPILER_IS_GNUCC) 12 | 13 | 14 | add_library(lzss-lib STATIC lzss-lib.c) 15 | 16 | 17 | add_executable(CompileMLKTool CompileMLKTool.c) 18 | install(TARGETS CompileMLKTool RUNTIME DESTINATION "bin") 19 | 20 | add_executable(CompileWLKTool CompileWLKTool.c) 21 | install(TARGETS CompileWLKTool RUNTIME DESTINATION "bin") 22 | 23 | add_executable(danbidec danbidec.c) 24 | install(TARGETS danbidec RUNTIME DESTINATION "bin") 25 | 26 | add_executable(DiamondRushExtract DiamondRushExtract.c) 27 | install(TARGETS DiamondRushExtract RUNTIME DESTINATION "bin") 28 | 29 | add_executable(DIMUnpack DIMUnpack.c) 30 | install(TARGETS DIMUnpack RUNTIME DESTINATION "bin") 31 | 32 | add_executable(FoxRangerExtract FoxRangerExtract.c) 33 | install(TARGETS FoxRangerExtract RUNTIME DESTINATION "bin") 34 | 35 | add_executable(gensqu_dec gensqu_dec.c) 36 | install(TARGETS gensqu_dec RUNTIME DESTINATION "bin") 37 | 38 | add_executable(kenji_dec kenji_dec.c) 39 | install(TARGETS kenji_dec RUNTIME DESTINATION "bin") 40 | 41 | add_executable(LBXUnpack LBXUnpack.c) 42 | install(TARGETS LBXUnpack RUNTIME DESTINATION "bin") 43 | 44 | add_executable(lzss-tool lzss-tool.c lzss-lib) 45 | install(TARGETS lzss-tool RUNTIME DESTINATION "bin") 46 | 47 | add_executable(mrndec mrndec.c) 48 | install(TARGETS mrndec RUNTIME DESTINATION "bin") 49 | 50 | add_executable(piyo_dec piyo_dec.c) 51 | install(TARGETS piyo_dec RUNTIME DESTINATION "bin") 52 | 53 | add_executable(rekiai_dec rekiai_dec.c) 54 | install(TARGETS rekiai_dec RUNTIME DESTINATION "bin") 55 | 56 | add_executable(wolfteam_dec wolfteam_dec.c) 57 | install(TARGETS wolfteam_dec RUNTIME DESTINATION "bin") 58 | 59 | add_executable(x68k_sps_dec x68k_sps_dec.c) 60 | install(TARGETS x68k_sps_dec RUNTIME DESTINATION "bin") 61 | 62 | add_executable(xordec xordec.c) 63 | install(TARGETS xordec RUNTIME DESTINATION "bin") 64 | -------------------------------------------------------------------------------- /CompileMLKTool.c: -------------------------------------------------------------------------------- 1 | // Compile MLK archive tool 2 | // ------------------------ 3 | // Valley Bell, written on 2022-11-19 4 | 5 | /* 6 | MLK Archive Format 7 | ------------------ 8 | 1 byte - number of files (n) 9 | 9*n bytes - file entry 10 | ?? bytes - MIDI data 11 | 12 | File Entry: 13 | 1 byte - loop mode (00 = no, 01 = yes) 14 | 4 bytes - start offset (absolute, Little Endian) 15 | 4 bytes - file length (Little Endian) 16 | 17 | MIDI notes: 18 | The "loop start" marker is "CC #31" (usually with value 0 on channel 1). It can be placed on any channel. 19 | It should be noted that the sound engine treats "Meta Event: Key Signature" (FF 59) as loop marker as well. 20 | 21 | With songs where multiple loop start markers, the game will use the last one. (see bad loop in Comet Summoner's Boss Theme) 22 | When no loop start marker exists, the song loops from the beginning. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef _MSC_VER 31 | #define strdup _strdup 32 | #endif 33 | 34 | #ifdef HAVE_STDINT 35 | 36 | #include 37 | typedef uint8_t UINT8; 38 | typedef uint16_t UINT16; 39 | typedef uint32_t UINT32; 40 | 41 | #else // ! HAVE_STDINT 42 | 43 | typedef unsigned char UINT8; 44 | typedef unsigned short UINT16; 45 | typedef unsigned int UINT32; 46 | 47 | #endif // HAVE_STDINT 48 | 49 | 50 | typedef struct _file_item 51 | { 52 | char* fileName; 53 | UINT8 loopMode; 54 | UINT32 filePos; 55 | UINT32 size; 56 | } FILE_ITEM; 57 | 58 | typedef struct _file_list 59 | { 60 | size_t alloc; 61 | size_t count; 62 | FILE_ITEM* items; 63 | } FILE_LIST; 64 | 65 | 66 | static UINT8 ReadFileData(const char* fileName, UINT32* retSize, UINT8** retData); 67 | static size_t GetFileSize(const char* fileName); 68 | static UINT8 WriteFileData(const char* fileName, UINT32 dataLen, const void* data); 69 | static const char* GetFileTitle(const char* filePath); 70 | static const char* GetFileExtension(const char* filePath); 71 | static int ExtractArchive(const char* arcFileName, const char* outPattern); 72 | static int CreateArchive(const char* arcFileName, const char* fileListName); 73 | static UINT16 ReadLE16(const UINT8* data); 74 | static UINT32 ReadLE32(const UINT8* data); 75 | static void WriteLE16(UINT8* buffer, UINT16 value); 76 | static void WriteLE32(UINT8* buffer, UINT32 value); 77 | 78 | 79 | #define MODE_NONE 0x00 80 | #define MODE_EXTRACT 0x01 81 | #define MODE_CREATE 0x02 82 | 83 | 84 | int main(int argc, char* argv[]) 85 | { 86 | int argbase; 87 | UINT8 mode; 88 | 89 | printf("Compile MLK archive tool\n------------------------\n"); 90 | if (argc < 2) 91 | { 92 | printf("Usage: %s [mode/options] archive.mlk out.mid/filelist.txt\n", argv[0]); 93 | printf("Mode: (required)\n"); 94 | printf(" -x extract archive, generates out00.mid, out01.mid, etc.\n"); 95 | printf(" -c create archive, read list of files from filelist.txt\n"); 96 | printf("Options:\n"); 97 | printf(" none\n"); 98 | return 0; 99 | } 100 | 101 | argbase = 1; 102 | mode = MODE_NONE; 103 | while(argbase < argc && argv[argbase][0] == '-') 104 | { 105 | if (argv[argbase][1] == 'x') 106 | { 107 | mode = MODE_EXTRACT; 108 | } 109 | else if (argv[argbase][1] == 'c') 110 | { 111 | mode = MODE_CREATE; 112 | } 113 | else 114 | break; 115 | argbase ++; 116 | } 117 | if (argc < argbase + 2) 118 | { 119 | printf("Insufficient parameters!\n"); 120 | return 0; 121 | } 122 | switch(mode) 123 | { 124 | case MODE_NONE: 125 | printf("Please specify a mode!\n"); 126 | return 1; 127 | case MODE_EXTRACT: 128 | return ExtractArchive(argv[argbase + 0], argv[argbase + 1]); 129 | case MODE_CREATE: 130 | return CreateArchive(argv[argbase + 0], argv[argbase + 1]); 131 | default: 132 | printf("Unsupported mode!\n"); 133 | return 1; 134 | } 135 | 136 | return 0; 137 | } 138 | 139 | static UINT8 ReadFileData(const char* fileName, UINT32* retSize, UINT8** retData) 140 | { 141 | FILE* hFile; 142 | UINT32 readBytes; 143 | 144 | hFile = fopen(fileName, "rb"); 145 | if (hFile == NULL) 146 | return 0xFF; 147 | 148 | fseek(hFile, 0, SEEK_END); 149 | *retSize = ftell(hFile); 150 | if (*retSize > 0x10000000) 151 | *retSize = 0x10000000; // limit to 256 MB 152 | 153 | *retData = (UINT8*)realloc(*retData, *retSize); 154 | fseek(hFile, 0, SEEK_SET); 155 | readBytes = fread(*retData, 0x01, *retSize, hFile); 156 | 157 | fclose(hFile); 158 | return (readBytes == *retSize) ? 0 : 1; 159 | } 160 | 161 | static size_t GetFileSize(const char* fileName) 162 | { 163 | FILE* hFile; 164 | size_t fileSize; 165 | 166 | hFile = fopen(fileName, "rb"); 167 | if (hFile == NULL) 168 | return (size_t)-1; 169 | 170 | fseek(hFile, 0, SEEK_END); 171 | fileSize = ftell(hFile); 172 | 173 | fclose(hFile); 174 | return fileSize; 175 | } 176 | 177 | static UINT8 WriteFileData(const char* fileName, UINT32 dataLen, const void* data) 178 | { 179 | FILE* hFile; 180 | UINT32 writtenBytes; 181 | 182 | hFile = fopen(fileName, "wb"); 183 | if (hFile == NULL) 184 | return 0xFF; 185 | 186 | writtenBytes = fwrite(data, 1, dataLen, hFile); 187 | 188 | fclose(hFile); 189 | return (writtenBytes == dataLen) ? 0 : 1; 190 | } 191 | 192 | static const char* GetFileTitle(const char* filePath) 193 | { 194 | const char* sepPos1 = strrchr(filePath, '/'); 195 | const char* sepPos2 = strrchr(filePath, '\\'); 196 | const char* dirSepPos; 197 | 198 | if (sepPos1 == NULL) 199 | dirSepPos = sepPos2; 200 | else if (sepPos2 == NULL) 201 | dirSepPos = sepPos1; 202 | else 203 | dirSepPos = (sepPos1 < sepPos2) ? sepPos2 : sepPos1; 204 | return (dirSepPos != NULL) ? &dirSepPos[1] : filePath; 205 | } 206 | 207 | static const char* GetFileExtension(const char* filePath) 208 | { 209 | const char* fileTitle = GetFileTitle(filePath); 210 | const char* extDotPos = strrchr(fileTitle, '.'); 211 | return extDotPos; 212 | } 213 | 214 | 215 | static int ExtractArchive(const char* arcFileName, const char* outPattern) 216 | { 217 | size_t arcSize; 218 | UINT8* arcData; 219 | UINT8 retVal; 220 | const char* fileExt; 221 | char* outName; 222 | char* outExt; 223 | FILE* hListFile; 224 | UINT32 tocPos; 225 | size_t fileCnt; 226 | size_t curFile; 227 | 228 | fileExt = GetFileExtension(outPattern); 229 | if (fileExt == NULL) 230 | fileExt = outPattern + strlen(outPattern); 231 | outName = (char*)malloc(strlen(outPattern) + 0x10); 232 | strcpy(outName, outPattern); 233 | outExt = outName + (fileExt - outPattern); 234 | 235 | arcSize = 0; 236 | arcData = NULL; 237 | retVal = ReadFileData(arcFileName, &arcSize, &arcData); 238 | if (retVal) 239 | { 240 | if (retVal == 0xFF) 241 | printf("Error opening %s!\n", arcFileName); 242 | else 243 | printf("Unable to fully read %s!\n", arcFileName); 244 | return 1; 245 | } 246 | 247 | // extract everything 248 | strcpy(outExt, ".txt"); 249 | hListFile = fopen(outName, "wt"); 250 | 251 | fileCnt = arcData[0x00]; 252 | fprintf(hListFile, "#filename\tloop\n"); 253 | printf("%u %s\n", fileCnt, (fileCnt == 1) ? "file" : "files"); 254 | 255 | tocPos = 0x01; 256 | for (curFile = 0; curFile < fileCnt; curFile ++, tocPos += 0x09) 257 | { 258 | UINT8 loopMode = arcData[tocPos + 0x00]; 259 | UINT32 filePos = ReadLE32(&arcData[tocPos + 0x01]); 260 | UINT32 fileSize = ReadLE32(&arcData[tocPos + 0x05]); 261 | 262 | // generate file name(ABC.ext -> ABC00.ext) 263 | sprintf(outExt, "%02u%s", curFile, fileExt); 264 | printf("File %u/%u: offset: 0x%06X, size 0x%04X\n", 1 + curFile, fileCnt, filePos, fileSize); 265 | 266 | fprintf(hListFile, "%s\t%u\n", outName, loopMode); 267 | retVal = WriteFileData(outName, fileSize, &arcData[filePos]); 268 | if (retVal) 269 | { 270 | if (retVal == 0xFF) 271 | printf("Error writing %s!\n", arcFileName); 272 | else 273 | printf("Error writing %s - file incomplete!\n", arcFileName); 274 | continue; 275 | } 276 | } 277 | 278 | fclose(hListFile); 279 | free(arcData); 280 | 281 | printf("Done.\n"); 282 | return 0; 283 | } 284 | 285 | static FILE_ITEM* AddFileListItem(FILE_LIST* fl) 286 | { 287 | if (fl->count >= fl->alloc) 288 | { 289 | fl->alloc += 0x10; 290 | fl->items = (FILE_ITEM*)realloc(fl->items, fl->alloc * sizeof(FILE_ITEM)); 291 | } 292 | fl->count ++; 293 | return &fl->items[fl->count - 1]; 294 | } 295 | 296 | static void FreeFileList(FILE_LIST* fl) 297 | { 298 | size_t curFile; 299 | for (curFile = 0; curFile < fl->count; curFile ++) 300 | { 301 | free(fl->items[curFile].fileName); 302 | } 303 | return; 304 | } 305 | 306 | static void RemoveControlChars(char* str) 307 | { 308 | size_t idx = strlen(str); 309 | 310 | while(idx > 0 && (unsigned char)str[idx - 1] < 0x20) 311 | idx --; 312 | str[idx] = '\0'; 313 | 314 | return; 315 | } 316 | 317 | static size_t GetColumns(char* line, size_t maxCols, char* colPtrs[], const char* delim) 318 | { 319 | size_t curCol = 0; 320 | char* token = line; 321 | while(token != NULL && curCol < maxCols) 322 | { 323 | colPtrs[curCol] = token; 324 | curCol ++; 325 | 326 | token = strpbrk(token, "\t"); 327 | if (token != NULL) 328 | { 329 | *token = '\0'; 330 | token ++; 331 | } 332 | } 333 | return curCol; 334 | } 335 | 336 | static int CreateArchive(const char* arcFileName, const char* fileListName) 337 | { 338 | FILE_LIST fileList; 339 | FILE* hFile; 340 | UINT32 lineID; 341 | char lineStr[0x1000]; // 4096 chars should be enough 342 | int result; 343 | 344 | hFile = fopen(fileListName, "rt"); 345 | if (hFile == NULL) 346 | { 347 | printf("Error opening %s!\n", fileListName); 348 | return 0xFF; 349 | } 350 | 351 | // read file list 352 | fileList.alloc = 0; 353 | fileList.count = 0; 354 | fileList.items = NULL; 355 | lineID = 0; 356 | while(! feof(hFile)) 357 | { 358 | FILE_ITEM* fi; 359 | char* strPtr; 360 | char* colPtrs[2]; 361 | size_t colCnt; 362 | 363 | strPtr = fgets(lineStr, 0x1000, hFile); 364 | if (strPtr == NULL) 365 | break; 366 | lineID ++; 367 | RemoveControlChars(lineStr); 368 | if (strlen(lineStr) == 0 || lineStr[0] == '#') 369 | continue; 370 | 371 | colCnt = GetColumns(lineStr, 2, colPtrs, "\t"); 372 | if (colCnt < 1) 373 | continue; 374 | 375 | fi = AddFileListItem(&fileList); 376 | fi->fileName = strdup(lineStr); 377 | if (colCnt >= 2) 378 | fi->loopMode = atoi(colPtrs[1]); 379 | else 380 | fi->loopMode = 0x00; 381 | } 382 | 383 | fclose(hFile); 384 | 385 | printf("Packing %u %s ...\n", fileList.count, (fileList.count == 1) ? "file" : "files"); 386 | // go through all files, determining file sizes and archive file data offsets 387 | { 388 | size_t curFile; 389 | UINT32 filePos; 390 | 391 | filePos = 0x01 + fileList.count * 0x09; 392 | for (curFile = 0; curFile < fileList.count; curFile ++) 393 | { 394 | FILE_ITEM* fi = &fileList.items[curFile]; 395 | size_t fileSize = GetFileSize(fi->fileName); 396 | 397 | fi->filePos = filePos; 398 | if (fileSize == (size_t)-1) 399 | fi->size = 0; // The error message will be printed later. 400 | else 401 | fi->size = (UINT32)fileSize; 402 | filePos += fi->size; 403 | } 404 | } 405 | 406 | hFile = fopen(arcFileName, "wb"); 407 | if (hFile == NULL) 408 | { 409 | printf("Error writing %s!\n", arcFileName); 410 | FreeFileList(&fileList); 411 | return 1; 412 | } 413 | 414 | result = 0; 415 | { 416 | size_t curFile; 417 | UINT32 tocPos; 418 | UINT32 dataSize; 419 | UINT8* data; 420 | UINT8 retVal; 421 | 422 | dataSize = 0x02 + fileList.count * 0x09; 423 | data = (UINT8*)malloc(dataSize); 424 | 425 | // generate archive TOC 426 | printf("Writing TOC ...\n"); 427 | tocPos = 0x00; 428 | data[tocPos] = (UINT8)fileList.count; tocPos += 0x01; 429 | for (curFile = 0; curFile < fileList.count; curFile ++, tocPos += 0x09) 430 | { 431 | FILE_ITEM* fi = &fileList.items[curFile]; 432 | data[tocPos + 0x00] = fi->loopMode; 433 | WriteLE32(&data[tocPos + 0x01], fi->filePos); 434 | WriteLE32(&data[tocPos + 0x05], fi->size); 435 | } 436 | fwrite(data, 1, dataSize, hFile); // write header data 437 | 438 | // copy file data into archive 439 | for (curFile = 0; curFile < fileList.count; curFile ++) 440 | { 441 | FILE_ITEM* fi = &fileList.items[curFile]; 442 | printf("Writing data %u/%u (%s) ...\n", 1 + curFile, fileList.count, fi->fileName); 443 | retVal = ReadFileData(fi->fileName, &dataSize, &data); 444 | if (retVal) 445 | { 446 | printf("Unable to read %s!\n", fi->fileName); 447 | result = 2; 448 | continue; 449 | } 450 | 451 | fseek(hFile, fi->filePos, SEEK_SET); 452 | fwrite(data, 1, dataSize, hFile); 453 | } 454 | free(data); 455 | } 456 | 457 | fclose(hFile); 458 | FreeFileList(&fileList); 459 | 460 | printf("Done.\n"); 461 | return result; 462 | } 463 | 464 | 465 | static UINT16 ReadLE16(const UINT8* data) 466 | { 467 | return (data[0x00] << 0) | (data[0x01] << 8); 468 | } 469 | 470 | static UINT32 ReadLE32(const UINT8* data) 471 | { 472 | return (data[0x00] << 0) | (data[0x01] << 8) | 473 | (data[0x02] << 16) | (data[0x03] << 24); 474 | } 475 | 476 | static void WriteLE16(UINT8* buffer, UINT16 value) 477 | { 478 | buffer[0x00] = (value >> 0) & 0xFF; 479 | buffer[0x01] = (value >> 8) & 0xFF; 480 | return; 481 | } 482 | 483 | static void WriteLE32(UINT8* buffer, UINT32 value) 484 | { 485 | buffer[0x00] = (value >> 0) & 0xFF; 486 | buffer[0x01] = (value >> 8) & 0xFF; 487 | buffer[0x02] = (value >> 16) & 0xFF; 488 | buffer[0x03] = (value >> 24) & 0xFF; 489 | return; 490 | } 491 | -------------------------------------------------------------------------------- /CompileWLKTool.c: -------------------------------------------------------------------------------- 1 | // Compile WLK archive tool 2 | // ------------------------ 3 | // Valley Bell, written on 2022-11-20 4 | 5 | /* 6 | WLK Archive Format v1 7 | ------------------ 8 | 2 bytes - [number of files]-1 (n = value+1) (Little Endian) 9 | 0Eh*n bytes - file entry 10 | ?? bytes - sound data 11 | 12 | File Entry: 13 | 1 byte - ?? (usually 00) 14 | 1 byte - flags 15 | Bit 6 (40) - ?? 16 | Bit 7 (80) - 16-bit (clear = 8-bit) 17 | 4 bytes - start offset (absolute, Little Endian) 18 | 4 bytes - file length (Little Endian) 19 | 4 bytes - sample rate (Little Endian) 20 | 21 | WLK Archive Format v2 22 | ------------------ 23 | 8 bytes - "WLKF0200" 24 | 2 bytes - number of files (n) 25 | 2 bytes - flags 26 | Bit 0 (01) - TOC contains file titles? 27 | Bit 1 (02) - TOC contains source file paths? 28 | ??*n bytes - file entry 29 | The file entry is variable, based on the main header flags. 30 | Base size: 16h 31 | contains file title: add 6 32 | contains source file path: add 6 33 | Thus the entries can have from 16h to 22h bytes. 34 | ?? bytes - sound data 35 | 36 | File Entry: 37 | 1 byte - ?? (usually FF) 38 | 1 byte - flags 39 | Bit 0 (01) - ?? 40 | Bit 7 (80) - 16-bit (clear = 8-bit) 41 | 4 bytes - start offset (absolute, Little Endian) 42 | 4 bytes - file length (Little Endian) 43 | 4 bytes - sample rate (Little Endian) 44 | 4 bytes - ?? (usually 0) 45 | 4 bytes - ?? (usually 0) 46 | [if file title is enabled] 47 | 4 bytes - file title offset (absolute, Little Endian), destination string has no terminator 48 | 2 bytes - file title length (Little Endian) 49 | [if source file path is enabled] 50 | 4 bytes - source file path offset (absolute, Little Endian), destination string has no terminator 51 | 2 bytes - source file path length (Little Endian) 52 | */ 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #ifdef _WIN32 60 | #include 61 | #undef GetFileTitle // we have our own platform-independent GetFileTitle fuction 62 | #else 63 | #include // for PATH_MAX 64 | // realpath() is part of stdlib.h 65 | #endif 66 | 67 | #ifdef _MSC_VER 68 | #define strdup _strdup 69 | #endif 70 | 71 | #ifdef HAVE_STDINT 72 | 73 | #include 74 | typedef uint8_t UINT8; 75 | typedef uint16_t UINT16; 76 | typedef uint32_t UINT32; 77 | 78 | #else // ! HAVE_STDINT 79 | 80 | typedef unsigned char UINT8; 81 | typedef unsigned short UINT16; 82 | typedef unsigned int UINT32; 83 | 84 | #endif // HAVE_STDINT 85 | 86 | 87 | typedef struct _file_item 88 | { 89 | char* fileName; 90 | UINT8 status; 91 | UINT8 flags; 92 | UINT32 filePos; 93 | UINT32 size; 94 | UINT32 smplRate; 95 | UINT32 wavDataOfs; 96 | char* fileTitle; 97 | char* srcFilePath; 98 | } FILE_ITEM; 99 | typedef struct _file_path_info 100 | { 101 | UINT32 titleOfs; 102 | UINT32 titleLen; 103 | UINT32 pathOfs; 104 | UINT32 pathLen; 105 | } FILEPATH_INFO; 106 | 107 | typedef struct _file_list 108 | { 109 | size_t alloc; 110 | size_t count; 111 | FILE_ITEM* items; 112 | } FILE_LIST; 113 | 114 | 115 | static UINT8 ReadFileData(const char* fileName, UINT32* retSize, UINT8** retData); 116 | static UINT8 GetWaveInfo(const char* fileName, FILE_ITEM* fi); 117 | static UINT8 WriteFileData(const char* fileName, UINT32 dataLen, const void* data); 118 | static UINT8 WriteWaveFile(const char* fileName, const FILE_ITEM* info, const void* data); 119 | static char* GetFullFilePath(const char* relFilePath); 120 | static const char* GetFileTitle(const char* filePath); 121 | static const char* GetFileExtension(const char* filePath); 122 | static int ExtractArchive(const char* arcFileName, const char* outPattern); 123 | static int CreateArchive(const char* arcFileName, const char* fileListName); 124 | static UINT16 ReadLE16(const UINT8* data); 125 | static UINT32 ReadLE32(const UINT8* data); 126 | static void WriteLE16(UINT8* buffer, UINT16 value); 127 | static void WriteLE32(UINT8* buffer, UINT32 value); 128 | 129 | 130 | #define MODE_NONE 0x00 131 | #define MODE_EXTRACT 0x01 132 | #define MODE_CREATE 0x02 133 | 134 | #define ARC_FMT_NONE 0x00 135 | #define ARC_FMT_OLD 0x01 136 | #define ARC_FMT_NEW 0x02 137 | 138 | 139 | static UINT8 packArcType = ARC_FMT_NEW; 140 | static UINT16 packArcFlags = 0x0003; 141 | static UINT8 useFileTitle = 0; 142 | 143 | int main(int argc, char* argv[]) 144 | { 145 | int argbase; 146 | UINT8 mode; 147 | 148 | printf("Compile WLK archive tool\n------------------------\n"); 149 | if (argc < 2) 150 | { 151 | printf("Usage: %s [mode/options] archive.mlk out.mid/filelist.txt\n", argv[0]); 152 | printf("Mode: (required)\n"); 153 | printf(" -x extract archive, generates out00.wav, out01.wav, etc.\n"); 154 | printf(" -c create archive, read list of files from filelist.txt\n"); 155 | printf("Options:\n"); 156 | printf(" -n [extract] name extracted files after original file title\n"); 157 | printf(" Note: Shift-JIS names may fail to save on Western systems\n"); 158 | printf(" -n [create] save true file name/path to archive\n"); 159 | printf(" default/not set: take file name/path from filelist.txt\n"); 160 | printf(" -f n set archive format version (can be 1/2, default: %u)\n", packArcType); 161 | printf(" -b n set archive header flags (default: 0x%02X)\n", packArcFlags); 162 | return 0; 163 | } 164 | 165 | argbase = 1; 166 | mode = MODE_NONE; 167 | while(argbase < argc && argv[argbase][0] == '-') 168 | { 169 | if (argv[argbase][1] == 'x') 170 | { 171 | mode = MODE_EXTRACT; 172 | } 173 | else if (argv[argbase][1] == 'c') 174 | { 175 | mode = MODE_CREATE; 176 | } 177 | else if (argv[argbase][1] == 'n') 178 | { 179 | useFileTitle = 1; 180 | } 181 | else if (argv[argbase][1] == 'f') 182 | { 183 | argbase ++; 184 | if (argbase < argc) 185 | packArcType = (UINT8)strtoul(argv[argbase], NULL, 0); 186 | } 187 | else if (argv[argbase][1] == 'b') 188 | { 189 | argbase ++; 190 | if (argbase < argc) 191 | packArcFlags = (UINT16)strtoul(argv[argbase], NULL, 0); 192 | } 193 | else 194 | break; 195 | argbase ++; 196 | } 197 | if (argc < argbase + 2) 198 | { 199 | printf("Insufficient parameters!\n"); 200 | return 0; 201 | } 202 | switch(mode) 203 | { 204 | case MODE_NONE: 205 | printf("Please specify a mode!\n"); 206 | return 1; 207 | case MODE_EXTRACT: 208 | return ExtractArchive(argv[argbase + 0], argv[argbase + 1]); 209 | case MODE_CREATE: 210 | return CreateArchive(argv[argbase + 0], argv[argbase + 1]); 211 | default: 212 | printf("Unsupported mode!\n"); 213 | return 1; 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | static UINT8 ReadFileData(const char* fileName, UINT32* retSize, UINT8** retData) 220 | { 221 | FILE* hFile; 222 | UINT32 readBytes; 223 | 224 | hFile = fopen(fileName, "rb"); 225 | if (hFile == NULL) 226 | return 0xFF; 227 | 228 | fseek(hFile, 0, SEEK_END); 229 | *retSize = ftell(hFile); 230 | if (*retSize > 0x10000000) 231 | *retSize = 0x10000000; // limit to 256 MB 232 | 233 | *retData = (UINT8*)realloc(*retData, *retSize); 234 | fseek(hFile, 0, SEEK_SET); 235 | readBytes = fread(*retData, 0x01, *retSize, hFile); 236 | 237 | fclose(hFile); 238 | return (readBytes == *retSize) ? 0 : 1; 239 | } 240 | 241 | static UINT8 GetWaveInfo(const char* fileName, FILE_ITEM* fi) 242 | { 243 | FILE* hFile; 244 | UINT8 chnkHdr[8]; 245 | UINT32 chnkSize; 246 | UINT8 found; 247 | UINT8 fmtData[0x10]; 248 | size_t readEl; 249 | UINT16 formatTag; 250 | UINT16 nChannels; 251 | UINT16 bitsPerSmpl; 252 | 253 | hFile = fopen(fileName, "rb"); 254 | if (hFile == NULL) 255 | return 0xFF; // open error 256 | 257 | readEl = fread(chnkHdr, 4, 2, hFile); 258 | if (readEl < 2 || memcmp(&chnkHdr[0], "RIFF", 4)) 259 | { 260 | fclose(hFile); 261 | return 0xF0; // no RIFF file 262 | } 263 | readEl = fread(chnkHdr, 4, 1, hFile); 264 | if (readEl < 1 || memcmp(&chnkHdr[0], "WAVE", 4)) 265 | { 266 | fclose(hFile); 267 | return 0xF1; // no RIFF WAVE 268 | } 269 | 270 | found = 0x00; 271 | while(!feof(hFile) && !ferror(hFile)) 272 | { 273 | size_t fPos; 274 | readEl = fread(chnkHdr, 4, 2, hFile); 275 | if (readEl < 2) 276 | break; 277 | chnkSize = ReadLE32(&chnkHdr[4]); 278 | 279 | fPos = ftell(hFile); 280 | if (!memcmp(&chnkHdr[0], "data", 4)) 281 | { 282 | found |= 0x02; 283 | fi->wavDataOfs = (UINT32)ftell(hFile); 284 | fi->size = chnkSize; 285 | break; 286 | } 287 | else if (!memcmp(&chnkHdr[0], "fmt ", 4)) 288 | { 289 | found |= 0x01; 290 | fread(fmtData, 1, 0x10, hFile); 291 | } 292 | fseek(hFile, fPos + chnkSize, SEEK_SET); 293 | } 294 | 295 | fclose(hFile); 296 | if (!(found & 0x01)) 297 | return 0xF2; // Format chunk not found. 298 | if (!(found & 0x02)) 299 | return 0xF3; // Data chunk not found. 300 | 301 | formatTag = ReadLE16(&fmtData[0x00]); 302 | nChannels = ReadLE16(&fmtData[0x02]); 303 | bitsPerSmpl = ReadLE16(&fmtData[0x0E]); 304 | if (formatTag != 0x0001) // WAVE_FORMAT_PCM 305 | return 0x80; // codec not supported 306 | if (nChannels != 1) 307 | return 0x81; // must be mono 308 | if (bitsPerSmpl != 8 && bitsPerSmpl != 16) 309 | return 0x82; // needs to be 8-bit or 16-bit 310 | fi->smplRate = ReadLE32(&fmtData[0x04]); 311 | fi->flags &= ~0x80; 312 | fi->flags |= (bitsPerSmpl == 16) ? 0x80 : 0x00; 313 | 314 | return 0x00; 315 | } 316 | 317 | static UINT8 WriteFileData(const char* fileName, UINT32 dataLen, const void* data) 318 | { 319 | FILE* hFile; 320 | UINT32 writtenBytes; 321 | 322 | hFile = fopen(fileName, "wb"); 323 | if (hFile == NULL) 324 | return 0xFF; 325 | 326 | writtenBytes = fwrite(data, 1, dataLen, hFile); 327 | 328 | fclose(hFile); 329 | return (writtenBytes == dataLen) ? 0 : 1; 330 | } 331 | 332 | static UINT8 WriteWaveFile(const char* fileName, const FILE_ITEM* info, const void* data) 333 | { 334 | FILE* hFile; 335 | UINT8 wavHdr[0x2C]; 336 | UINT32 writtenBytes; 337 | UINT32 fileSize; 338 | UINT8 channels; 339 | UINT16 bitDepth; 340 | UINT16 blockSize; 341 | UINT32 byteRate; 342 | 343 | hFile = fopen(fileName, "wb"); 344 | if (hFile == NULL) 345 | return 0xFF; 346 | 347 | channels = 1; 348 | bitDepth = (info->flags & 0x80) ? 16 : 8; 349 | blockSize = (channels * bitDepth + 7) / 8; 350 | byteRate = info->smplRate * blockSize; 351 | 352 | memcpy(&wavHdr[0x00], "RIFF", 0x04); 353 | WriteLE32(&wavHdr[0x04], 0x24 + info->size); // RIFF chunk size 354 | memcpy(&wavHdr[0x08], "WAVE", 0x04); 355 | 356 | memcpy(&wavHdr[0x0C], "fmt ", 0x04); 357 | WriteLE32(&wavHdr[0x10], 0x10); // fmt chunk size 358 | WriteLE16(&wavHdr[0x14], 0x0001); // format tag: WAVE_FORMAT_PCM 359 | WriteLE16(&wavHdr[0x16], channels); // number of channels 360 | WriteLE32(&wavHdr[0x18], info->smplRate); // sample rate 361 | WriteLE32(&wavHdr[0x1C], byteRate); // bytes per second 362 | WriteLE16(&wavHdr[0x20], blockSize); // block align 363 | WriteLE16(&wavHdr[0x22], bitDepth); // bits per sample 364 | 365 | memcpy(&wavHdr[0x24], "data", 0x04); 366 | WriteLE32(&wavHdr[0x28], info->size); // data chunk size 367 | 368 | fileSize = 0x2C + info->size; 369 | 370 | writtenBytes = fwrite(wavHdr, 1, 0x2C, hFile); 371 | writtenBytes += fwrite(data, 1, info->size, hFile); 372 | 373 | fclose(hFile); 374 | return (writtenBytes == fileSize) ? 0 : 1; 375 | } 376 | 377 | static char* GetFullFilePath(const char* relFilePath) 378 | { 379 | #ifdef _WIN32 380 | char buffer[MAX_PATH]; 381 | DWORD pathLen = GetFullPathName(relFilePath, MAX_PATH, buffer, NULL); 382 | if (!pathLen) 383 | return NULL; 384 | return strdup(buffer); 385 | #else 386 | return realpath(relFilePath, NULL); 387 | #endif 388 | } 389 | 390 | static const char* GetFileTitle(const char* filePath) 391 | { 392 | const char* sepPos1 = strrchr(filePath, '/'); 393 | const char* sepPos2 = strrchr(filePath, '\\'); 394 | const char* dirSepPos; 395 | 396 | if (sepPos1 == NULL) 397 | dirSepPos = sepPos2; 398 | else if (sepPos2 == NULL) 399 | dirSepPos = sepPos1; 400 | else 401 | dirSepPos = (sepPos1 < sepPos2) ? sepPos2 : sepPos1; 402 | return (dirSepPos != NULL) ? &dirSepPos[1] : filePath; 403 | } 404 | 405 | static const char* GetFileExtension(const char* filePath) 406 | { 407 | const char* fileTitle = GetFileTitle(filePath); 408 | const char* extDotPos = strrchr(fileTitle, '.'); 409 | return extDotPos; 410 | } 411 | 412 | 413 | static int ExtractArchive(const char* arcFileName, const char* outPattern) 414 | { 415 | UINT8 arcFormat; 416 | UINT16 arcHdrFlags; 417 | UINT16 tocEntryBS; // base size 418 | UINT16 tocEntrySize; 419 | UINT32 arcSize; 420 | UINT8* arcData; 421 | UINT8 retVal; 422 | const char* fileExt; 423 | char* outName; 424 | char* outExt; 425 | FILE* hListFile; 426 | UINT32 tocPos; 427 | size_t fileCnt; 428 | size_t curFile; 429 | 430 | fileExt = GetFileExtension(outPattern); 431 | if (fileExt == NULL) 432 | fileExt = outPattern + strlen(outPattern); 433 | outName = (char*)malloc(strlen(outPattern) + 0x10); 434 | strcpy(outName, outPattern); 435 | outExt = outName + (fileExt - outPattern); 436 | 437 | arcSize = 0; 438 | arcData = NULL; 439 | retVal = ReadFileData(arcFileName, &arcSize, &arcData); 440 | if (retVal) 441 | { 442 | if (retVal == 0xFF) 443 | printf("Error opening %s!\n", arcFileName); 444 | else 445 | printf("Unable to fully read %s!\n", arcFileName); 446 | return 1; 447 | } 448 | 449 | arcFormat = ARC_FMT_NONE; 450 | if (! memcmp(&arcData[0x00], "WLKF0200", 0x08)) 451 | { 452 | arcFormat = ARC_FMT_NEW; 453 | } 454 | else 455 | { 456 | UINT16 fileCnt16 = ReadLE16(&arcData[0x00]) + 1; 457 | UINT32 file1Pos = ReadLE32(&arcData[0x04]); 458 | if (file1Pos >= (fileCnt16 * 0x0E) && file1Pos < arcSize) 459 | arcFormat = ARC_FMT_OLD; 460 | } 461 | 462 | printf("Archive format version: %u\n", arcFormat); 463 | switch(arcFormat) 464 | { 465 | case ARC_FMT_OLD: 466 | fileCnt = ReadLE16(&arcData[0x00]) + 1; 467 | arcHdrFlags = 0x00; 468 | tocEntryBS = 0x0E; 469 | tocEntrySize = tocEntryBS; 470 | tocPos = 0x02; 471 | break; 472 | case ARC_FMT_NEW: 473 | fileCnt = ReadLE16(&arcData[0x08]); 474 | arcHdrFlags = ReadLE16(&arcData[0x0A]); 475 | printf("Archive flags: 0x%04X\n", arcHdrFlags); 476 | tocEntryBS = 0x16; 477 | tocEntrySize = tocEntryBS; 478 | if (arcHdrFlags & 0x0001) 479 | tocEntrySize += 0x06; 480 | if (arcHdrFlags & 0x0002) 481 | tocEntrySize += 0x06; 482 | tocPos = 0x0C; 483 | break; 484 | default: 485 | printf("Unable to determine WLK format version!\n"); 486 | return 2; 487 | } 488 | 489 | // extract everything 490 | strcpy(outExt, ".txt"); 491 | hListFile = fopen(outName, "wt"); 492 | 493 | if (arcHdrFlags & 0x0003) 494 | fprintf(hListFile, "#filename\tflags\tsrcPath\tfileTitle\n"); 495 | else 496 | fprintf(hListFile, "#filename\tflags\n"); 497 | printf("%u %s\n", fileCnt, (fileCnt == 1) ? "file" : "files"); 498 | 499 | for (curFile = 0; curFile < fileCnt; curFile ++, tocPos += tocEntrySize) 500 | { 501 | UINT32 tpOfs; 502 | FILE_ITEM fi; 503 | char* outPath; 504 | 505 | fi.flags = arcData[tocPos + 0x01]; 506 | fi.filePos = ReadLE32(&arcData[tocPos + 0x02]); 507 | fi.size = ReadLE32(&arcData[tocPos + 0x06]); 508 | fi.smplRate = ReadLE32(&arcData[tocPos + 0x0A]); 509 | fi.fileTitle = NULL; 510 | fi.srcFilePath = NULL; 511 | tpOfs = tocEntryBS; 512 | 513 | if (arcHdrFlags & 0x0001) 514 | { 515 | UINT32 ofs = ReadLE32(&arcData[tocPos + tpOfs + 0x00]); 516 | UINT32 len = ReadLE16(&arcData[tocPos + tpOfs + 0x04]); 517 | if (ofs && len) 518 | { 519 | fi.fileTitle = (char*)malloc(len + 1); 520 | memcpy(fi.fileTitle, &arcData[ofs], len); 521 | fi.fileTitle[len] = '\0'; 522 | tpOfs += 0x06; 523 | } 524 | } 525 | if (arcHdrFlags & 0x0002) 526 | { 527 | UINT32 ofs = ReadLE32(&arcData[tocPos + tpOfs + 0x00]); 528 | UINT32 len = ReadLE16(&arcData[tocPos + tpOfs + 0x04]); 529 | if (ofs && len) 530 | { 531 | fi.srcFilePath = (char*)malloc(len + 1); 532 | memcpy(fi.srcFilePath, &arcData[ofs], len); 533 | fi.srcFilePath[len] = '\0'; 534 | tpOfs += 0x06; 535 | } 536 | } 537 | 538 | // generate file name(ABC.ext -> ABC00.ext) 539 | sprintf(outExt, "%02u%s", curFile, fileExt); 540 | printf("File %u/%u: offset: 0x%06X, size 0x%04X\n", 1 + curFile, fileCnt, fi.filePos, fi.size); 541 | 542 | if (useFileTitle && fi.fileTitle != NULL) 543 | { 544 | const char* titlePtr = GetFileTitle(outName); 545 | size_t prefixLen = titlePtr - outName; 546 | size_t totalLen = prefixLen + strlen(fi.fileTitle); 547 | outPath = (char*)malloc(totalLen + 1); 548 | strncpy(&outPath[0], outName, titlePtr - outName); 549 | strcpy(&outPath[prefixLen], fi.fileTitle); 550 | } 551 | else 552 | { 553 | outPath = outName; 554 | } 555 | if (arcHdrFlags & 0x0003) 556 | { 557 | fprintf(hListFile, "%s\t0x%02X\t%s\t%s\n", outPath, fi.flags, 558 | (fi.srcFilePath != NULL) ? fi.srcFilePath : "", 559 | (fi.fileTitle != NULL) ? fi.fileTitle : ""); 560 | } 561 | else 562 | { 563 | fprintf(hListFile, "%s\t0x%02X\n", outPath, fi.flags); 564 | } 565 | retVal = WriteWaveFile(outPath, &fi, &arcData[fi.filePos]); 566 | if (retVal) 567 | { 568 | if (retVal == 0xFF) 569 | printf("Error writing %s!\n", outPath); 570 | else 571 | printf("Error writing %s - file incomplete!\n", outPath); 572 | } 573 | if (outPath != outName) 574 | free(outPath); 575 | if (fi.fileTitle) 576 | free(fi.fileTitle); 577 | if (fi.srcFilePath) 578 | free(fi.srcFilePath); 579 | } 580 | 581 | fclose(hListFile); 582 | free(arcData); 583 | 584 | printf("Done.\n"); 585 | return 0; 586 | } 587 | 588 | static FILE_ITEM* AddFileListItem(FILE_LIST* fl) 589 | { 590 | if (fl->count >= fl->alloc) 591 | { 592 | fl->alloc += 0x10; 593 | fl->items = (FILE_ITEM*)realloc(fl->items, fl->alloc * sizeof(FILE_ITEM)); 594 | } 595 | fl->count ++; 596 | return &fl->items[fl->count - 1]; 597 | } 598 | 599 | static void FreeFileList(FILE_LIST* fl) 600 | { 601 | size_t curFile; 602 | for (curFile = 0; curFile < fl->count; curFile ++) 603 | { 604 | free(fl->items[curFile].fileName); 605 | free(fl->items[curFile].fileTitle); 606 | free(fl->items[curFile].srcFilePath); 607 | } 608 | return; 609 | } 610 | 611 | static void RemoveControlChars(char* str) 612 | { 613 | size_t idx = strlen(str); 614 | 615 | while(idx > 0 && (unsigned char)str[idx - 1] < 0x20) 616 | idx --; 617 | str[idx] = '\0'; 618 | 619 | return; 620 | } 621 | 622 | static size_t GetColumns(char* line, size_t maxCols, char* colPtrs[], const char* delim) 623 | { 624 | size_t curCol = 0; 625 | char* token = line; 626 | while(token != NULL && curCol < maxCols) 627 | { 628 | colPtrs[curCol] = token; 629 | curCol ++; 630 | 631 | token = strpbrk(token, "\t"); 632 | if (token != NULL) 633 | { 634 | *token = '\0'; 635 | token ++; 636 | } 637 | } 638 | return curCol; 639 | } 640 | 641 | static int CreateArchive(const char* arcFileName, const char* fileListName) 642 | { 643 | FILE_LIST fileList; 644 | FILE* hFile; 645 | UINT32 lineID; 646 | char lineStr[0x1000]; // 4096 chars should be enough 647 | UINT16 tocEntryBS; // base size 648 | UINT16 tocEntrySize; 649 | UINT32 tocSize; 650 | UINT32 payloadEndOfs; 651 | int result; 652 | 653 | hFile = fopen(fileListName, "rt"); 654 | if (hFile == NULL) 655 | { 656 | printf("Error opening %s!\n", fileListName); 657 | return 0xFF; 658 | } 659 | 660 | // read file list 661 | fileList.alloc = 0; 662 | fileList.count = 0; 663 | fileList.items = NULL; 664 | lineID = 0; 665 | while(! feof(hFile)) 666 | { 667 | FILE_ITEM* fi; 668 | char* strPtr; 669 | char* colPtrs[4]; 670 | size_t colCnt; 671 | 672 | strPtr = fgets(lineStr, 0x1000, hFile); 673 | if (strPtr == NULL) 674 | break; 675 | lineID ++; 676 | RemoveControlChars(lineStr); 677 | if (strlen(lineStr) == 0 || lineStr[0] == '#') 678 | continue; 679 | 680 | colCnt = GetColumns(lineStr, 4, colPtrs, "\t"); 681 | if (colCnt < 1) 682 | continue; 683 | 684 | fi = AddFileListItem(&fileList); 685 | fi->fileName = strdup(lineStr); 686 | if (colCnt >= 2) 687 | fi->flags = (UINT8)strtoul(colPtrs[1], NULL, 0); 688 | else 689 | fi->flags = 0x00; 690 | fi->srcFilePath = (colCnt >= 3) ? strdup(colPtrs[2]) : NULL; 691 | fi->fileTitle = (colCnt >= 4) ? strdup(colPtrs[3]) : NULL; 692 | } 693 | 694 | fclose(hFile); 695 | 696 | switch(packArcType) 697 | { 698 | case ARC_FMT_OLD: 699 | tocEntryBS = 0x0E; 700 | tocEntrySize = tocEntryBS; 701 | tocSize = 0x02 + fileList.count * tocEntrySize; 702 | break; 703 | case ARC_FMT_NEW: 704 | tocEntryBS = 0x16; 705 | tocEntrySize = tocEntryBS; 706 | if (packArcFlags & 0x0001) 707 | tocEntrySize += 0x06; 708 | if (packArcFlags & 0x0002) 709 | tocEntrySize += 0x06; 710 | tocSize = 0x0C + fileList.count * tocEntrySize; 711 | break; 712 | default: 713 | printf("Unable to determine WLK format version!\n"); 714 | FreeFileList(&fileList); 715 | return 9; 716 | } 717 | 718 | printf("Packing %u %s ...\n", fileList.count, (fileList.count == 1) ? "file" : "files"); 719 | // go through all files, determining file sizes and archive file data offsets 720 | { 721 | size_t curFile; 722 | UINT32 filePos; 723 | 724 | filePos = tocSize; 725 | for (curFile = 0; curFile < fileList.count; curFile ++) 726 | { 727 | FILE_ITEM* fi = &fileList.items[curFile]; 728 | fi->status = GetWaveInfo(fi->fileName, fi); 729 | // Potential error messages will be printed when doing the actual packaging. 730 | fi->filePos = filePos; 731 | if (fi->status & 0x80) 732 | fi->size = 0; 733 | else 734 | filePos += fi->size; 735 | 736 | if (useFileTitle && packArcType >= ARC_FMT_NEW) 737 | { 738 | free(fi->srcFilePath); 739 | free(fi->fileTitle); 740 | fi->srcFilePath = GetFullFilePath(fi->fileName); 741 | fi->fileTitle = strdup(GetFileTitle(fi->srcFilePath)); 742 | } 743 | } 744 | payloadEndOfs = filePos; 745 | } 746 | 747 | hFile = fopen(arcFileName, "wb"); 748 | if (hFile == NULL) 749 | { 750 | printf("Error writing %s!\n", arcFileName); 751 | FreeFileList(&fileList); 752 | return 1; 753 | } 754 | 755 | result = 0; 756 | { 757 | size_t curFile; 758 | UINT32 tocPos; 759 | UINT32 dataSize; 760 | UINT8* data; 761 | FILEPATH_INFO* fpiList; 762 | UINT32 fnListSize; 763 | UINT8* fnListData; 764 | UINT8 retVal; 765 | 766 | data = (UINT8*)malloc(tocSize); 767 | fnListData = NULL; 768 | 769 | // generate archive TOC 770 | printf("Writing TOC ...\n"); 771 | switch(packArcType) 772 | { 773 | case ARC_FMT_OLD: 774 | tocPos = 0x00; 775 | WriteLE16(&data[tocPos], (UINT16)(fileList.count - 1)); tocPos += 0x02; 776 | for (curFile = 0; curFile < fileList.count; curFile ++, tocPos += tocEntrySize) 777 | { 778 | FILE_ITEM* fi = &fileList.items[curFile]; 779 | data[tocPos + 0x00] = 0x00; 780 | data[tocPos + 0x01] = fi->flags; 781 | WriteLE32(&data[tocPos + 0x02], fi->filePos); 782 | WriteLE32(&data[tocPos + 0x06], fi->size); 783 | WriteLE32(&data[tocPos + 0x0A], fi->smplRate); 784 | } 785 | break; 786 | case ARC_FMT_NEW: 787 | fpiList = NULL; 788 | if (packArcFlags & 0x0003) 789 | { 790 | // estimate file title/path lengths and offsets 791 | fpiList = (FILEPATH_INFO*)calloc(fileList.count, sizeof(FILEPATH_INFO)); 792 | fnListSize = 0x00; 793 | if (packArcFlags & 0x0001) 794 | { 795 | for (curFile = 0; curFile < fileList.count; curFile ++) 796 | { 797 | FILE_ITEM* fi = &fileList.items[curFile]; 798 | FILEPATH_INFO* fpi = &fpiList[curFile]; 799 | if (fi->fileTitle != NULL) 800 | { 801 | fpi->titleOfs = fnListSize; 802 | fpi->titleLen = strlen(fi->fileTitle); 803 | fnListSize += fpi->titleLen; 804 | } 805 | } 806 | } 807 | if (packArcFlags & 0x0002) 808 | { 809 | for (curFile = 0; curFile < fileList.count; curFile ++) 810 | { 811 | FILE_ITEM* fi = &fileList.items[curFile]; 812 | FILEPATH_INFO* fpi = &fpiList[curFile]; 813 | if (fi->srcFilePath != NULL) 814 | { 815 | fpi->pathOfs = fnListSize; 816 | fpi->pathLen = strlen(fi->srcFilePath); 817 | fnListSize += fpi->pathLen; 818 | } 819 | } 820 | } 821 | fnListData = (UINT8*)malloc(fnListSize); 822 | 823 | // generate file lists 824 | if (packArcFlags & 0x0001) 825 | { 826 | for (curFile = 0; curFile < fileList.count; curFile ++) 827 | { 828 | FILE_ITEM* fi = &fileList.items[curFile]; 829 | FILEPATH_INFO* fpi = &fpiList[curFile]; 830 | if (fi->fileTitle != NULL) 831 | memcpy(&fnListData[fpi->titleOfs], fi->fileTitle, fpi->titleLen); 832 | } 833 | } 834 | if (packArcFlags & 0x0002) 835 | { 836 | for (curFile = 0; curFile < fileList.count; curFile ++) 837 | { 838 | FILE_ITEM* fi = &fileList.items[curFile]; 839 | FILEPATH_INFO* fpi = &fpiList[curFile]; 840 | if (fi->srcFilePath != NULL) 841 | memcpy(&fnListData[fpi->pathOfs], fi->srcFilePath, fpi->pathLen); 842 | } 843 | } 844 | } 845 | 846 | tocPos = 0x00; 847 | memcpy(&data[tocPos], "WLKF0200", 0x08); tocPos += 0x08; 848 | WriteLE16(&data[tocPos], (UINT16)fileList.count); tocPos += 0x02; 849 | WriteLE16(&data[tocPos], packArcFlags); tocPos += 0x02; 850 | 851 | for (curFile = 0; curFile < fileList.count; curFile ++, tocPos += tocEntrySize) 852 | { 853 | FILE_ITEM* fi = &fileList.items[curFile]; 854 | UINT32 tpOfs; 855 | data[tocPos + 0x00] = 0xFF; 856 | data[tocPos + 0x01] = fi->flags; 857 | WriteLE32(&data[tocPos + 0x02], fi->filePos); 858 | WriteLE32(&data[tocPos + 0x06], fi->size); 859 | WriteLE32(&data[tocPos + 0x0A], fi->smplRate); 860 | WriteLE32(&data[tocPos + 0x0E], 0); 861 | WriteLE32(&data[tocPos + 0x12], 0); 862 | tpOfs = tocEntryBS; 863 | 864 | if (packArcFlags & 0x0001) 865 | { 866 | FILEPATH_INFO* fpi = &fpiList[curFile]; 867 | UINT32 ofs = fpi->titleLen ? (payloadEndOfs + fpi->titleOfs) : 0; 868 | UINT16 len = (UINT16)fpi->titleLen; 869 | WriteLE32(&data[tocPos + tpOfs + 0x00], ofs); 870 | WriteLE16(&data[tocPos + tpOfs + 0x04], len); 871 | tpOfs += 0x06; 872 | } 873 | if (packArcFlags & 0x0002) 874 | { 875 | FILEPATH_INFO* fpi = &fpiList[curFile]; 876 | UINT32 ofs = fpi->pathLen ? (payloadEndOfs + fpi->pathOfs) : 0; 877 | UINT16 len = (UINT16)fpi->pathLen; 878 | WriteLE32(&data[tocPos + tpOfs + 0x00], ofs); 879 | WriteLE16(&data[tocPos + tpOfs + 0x04], len); 880 | tpOfs += 0x06; 881 | } 882 | } 883 | free(fpiList); fpiList = NULL; 884 | break; 885 | } 886 | fwrite(data, 1, tocSize, hFile); // write header data 887 | 888 | // copy file data into archive 889 | for (curFile = 0; curFile < fileList.count; curFile ++) 890 | { 891 | FILE_ITEM* fi = &fileList.items[curFile]; 892 | printf("Writing data %u/%u (%s) ...\n", 1 + curFile, fileList.count, fi->fileName); 893 | retVal = ReadFileData(fi->fileName, &dataSize, &data); 894 | if (retVal) 895 | { 896 | printf("Unable to read %s!\n", fi->fileName); 897 | result = 2; 898 | continue; 899 | } 900 | if (dataSize <= fi->wavDataOfs) 901 | dataSize = 0; 902 | else 903 | dataSize -= fi->wavDataOfs; 904 | if (dataSize > fi->size) 905 | dataSize = fi->size; 906 | 907 | fseek(hFile, fi->filePos, SEEK_SET); 908 | fwrite(&data[fi->wavDataOfs], 1, dataSize, hFile); 909 | } 910 | 911 | // copy file name data 912 | if (fnListData != NULL) 913 | { 914 | printf("Writing file names ...\n"); 915 | fwrite(fnListData, 1, fnListSize, hFile); 916 | } 917 | 918 | free(data); 919 | free(fnListData); 920 | } 921 | 922 | fclose(hFile); 923 | FreeFileList(&fileList); 924 | 925 | printf("Done.\n"); 926 | return result; 927 | } 928 | 929 | 930 | static UINT16 ReadLE16(const UINT8* data) 931 | { 932 | return (data[0x00] << 0) | (data[0x01] << 8); 933 | } 934 | 935 | static UINT32 ReadLE32(const UINT8* data) 936 | { 937 | return (data[0x00] << 0) | (data[0x01] << 8) | 938 | (data[0x02] << 16) | (data[0x03] << 24); 939 | } 940 | 941 | static void WriteLE16(UINT8* buffer, UINT16 value) 942 | { 943 | buffer[0x00] = (value >> 0) & 0xFF; 944 | buffer[0x01] = (value >> 8) & 0xFF; 945 | return; 946 | } 947 | 948 | static void WriteLE32(UINT8* buffer, UINT32 value) 949 | { 950 | buffer[0x00] = (value >> 0) & 0xFF; 951 | buffer[0x01] = (value >> 8) & 0xFF; 952 | buffer[0x02] = (value >> 16) & 0xFF; 953 | buffer[0x03] = (value >> 24) & 0xFF; 954 | return; 955 | } 956 | -------------------------------------------------------------------------------- /DIMUnpack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #if defined(WIN32) || defined(__WINDOWS__) 7 | #include // for _mkdir() 8 | #else 9 | #include 10 | #define _mkdir(dir) mkdir(x, 0777) 11 | #endif 12 | 13 | #include "stdtype.h" 14 | 15 | #if defined(_MSC_VER) 16 | #define INLINE static __inline 17 | #elif defined(__GNUC__) 18 | #define INLINE static __inline__ 19 | #else 20 | #define INLINE static inline 21 | #endif 22 | 23 | 24 | #pragma pack(1) 25 | typedef struct _fat_boot_sector 26 | { 27 | UINT16 BytPerSect; 28 | UINT8 SectPerCluster; 29 | UINT16 ReservedSect; 30 | UINT8 NumFats; 31 | UINT16 RootDirEntries; 32 | UINT16 LogicalSect; 33 | UINT8 MediumDesc; 34 | UINT16 SectPerFat; 35 | UINT16 SectPerTrk; 36 | UINT16 Heads; 37 | UINT16 HiddenSect; 38 | UINT8 Reserved[13]; 39 | char DiskName[11]; 40 | char FileSysType[8]; 41 | } FAT_BOOTSECT; 42 | 43 | typedef struct _fat_file_entry 44 | { 45 | char Name[8]; 46 | char Extension[3]; 47 | UINT8 Attribute; 48 | UINT8 Reserved[10]; 49 | UINT16 Time; 50 | UINT16 Date; 51 | UINT16 StartCluster; 52 | UINT32 FileLength; 53 | } FAT_ENTRY; 54 | #pragma pack() 55 | 56 | 57 | UINT32 DimSize; 58 | UINT8* DimData; 59 | FAT_BOOTSECT BootSect; 60 | UINT32 ClusterBase; 61 | UINT16 ClusterSize; 62 | UINT16 FATEntries; 63 | UINT16* FATTbl; 64 | 65 | INLINE UINT16 ReadLE16(const UINT8* Data); 66 | INLINE UINT16 ReadBE16(const UINT8* Data); 67 | static void ReadFAT(UINT32 BasePos); 68 | static void ReadDirectory(UINT32 Cluster, UINT16 NumEntries, const char* BasePath, UINT8 Layer); 69 | static void BuildFilename(char* DestBuf, FAT_ENTRY* Entry); 70 | static void ExtractFile(const char* FileName, UINT16 Cluster, UINT32 FileSize); 71 | 72 | int main(int argc, char* argv[]) 73 | { 74 | FILE* hFile; 75 | char BootSig[0x11]; 76 | UINT8 BootFmt; 77 | UINT8 BaseSects; 78 | UINT16 RootDirSize; 79 | UINT32 StartPos; 80 | char* OutPath; 81 | 82 | if (argc <= 2) 83 | { 84 | printf("Usage: DIMUnpack.exe DiskImg.dim OutPath\\\n"); 85 | return 0; 86 | } 87 | 88 | hFile = fopen(argv[1], "rb"); 89 | if (hFile == NULL) 90 | return 1; 91 | 92 | fseek(hFile, 0x00, SEEK_END); 93 | DimSize = ftell(hFile); 94 | 95 | fseek(hFile, 0x00, SEEK_SET); 96 | DimData = (UINT8*)malloc(DimSize); 97 | fread(DimData, 0x01, DimSize, hFile); 98 | 99 | fclose(hFile); 100 | 101 | strncpy(BootSig, (char*)&DimData[0x102], 0x10); 102 | BootSig[0x10] = '\0'; 103 | printf("Disk Format:\t%s\n", BootSig); 104 | 105 | if (BootSig[0] == '\x90') 106 | { 107 | // Verified to work with: 108 | // "\x90X68IPL30" Asuka 120 Percent Burning Fest 109 | // "\x90NEC 2.00" Arcus Odyssey [Disk 2] 110 | BootFmt = 0x01; 111 | StartPos = 0x10B; 112 | BootSect.BytPerSect = ReadLE16( &DimData[StartPos + 0x00]); 113 | BootSect.SectPerCluster = DimData[StartPos + 0x02]; 114 | BootSect.ReservedSect = ReadLE16( &DimData[StartPos + 0x03]); 115 | BootSect.NumFats = DimData[StartPos + 0x05]; 116 | BootSect.RootDirEntries = ReadLE16( &DimData[StartPos + 0x06]); 117 | BootSect.LogicalSect = ReadLE16( &DimData[StartPos + 0x08]); 118 | BootSect.MediumDesc = DimData[StartPos + 0x0A]; 119 | BootSect.SectPerFat = ReadLE16( &DimData[StartPos + 0x0B]); 120 | BootSect.SectPerTrk = ReadLE16( &DimData[StartPos + 0x0D]); 121 | BootSect.Heads = ReadLE16( &DimData[StartPos + 0x0F]); 122 | BootSect.HiddenSect = ReadLE16( &DimData[StartPos + 0x11]); 123 | memcpy(BootSect.Reserved, &DimData[StartPos + 0x13], 0x0D); 124 | memcpy(BootSect.DiskName, &DimData[StartPos + 0x20], 0x0B); 125 | memcpy(BootSect.FileSysType, &DimData[StartPos + 0x2B], 0x08); 126 | } 127 | else if (! strncmp(BootSig, "Hudson soft", 11)) 128 | { 129 | BootFmt = 0x02; 130 | StartPos = 0x112; 131 | BootSect.BytPerSect = ReadBE16( &DimData[StartPos + 0x00]); 132 | BootSect.SectPerCluster = DimData[StartPos + 0x02]; 133 | BootSect.NumFats = DimData[StartPos + 0x03]; 134 | BootSect.ReservedSect = ReadBE16( &DimData[StartPos + 0x04]); 135 | BootSect.RootDirEntries = ReadBE16( &DimData[StartPos + 0x06]); 136 | BootSect.LogicalSect = ReadBE16( &DimData[StartPos + 0x08]); 137 | BootSect.MediumDesc = DimData[StartPos + 0x0A]; 138 | BootSect.SectPerFat = DimData[StartPos + 0x0B]; 139 | BootSect.SectPerTrk = 0; 140 | BootSect.Heads = 0; 141 | BootSect.HiddenSect = 0; 142 | memset(BootSect.Reserved, 0x00, 0x0D); 143 | memset(BootSect.DiskName, 0x00, 0x0B); 144 | memset(BootSect.FileSysType, 0x00, 0x08); 145 | } 146 | else 147 | { 148 | printf("Unknown disk format!\n"); 149 | free(DimData); 150 | return 2; 151 | } 152 | printf("Bytes per Sector:\t%hu\n", BootSect.BytPerSect); BootSect.BytPerSect=1024; 153 | printf("Boot Sectors:\t\t%hu\n", BootSect.ReservedSect); BootSect.ReservedSect=1; 154 | printf("RootDir Entries:\t%hu\n", BootSect.RootDirEntries); BootSect.RootDirEntries=192; 155 | printf("Sectors per Cluster:\t%hu\n", BootSect.SectPerCluster); BootSect.SectPerCluster=1; 156 | 157 | BootSect.NumFats = 2; BootSect.SectPerFat = 2; 158 | BaseSects = BootSect.ReservedSect + BootSect.NumFats * BootSect.SectPerFat; 159 | RootDirSize = BootSect.RootDirEntries * 32; 160 | RootDirSize = ((RootDirSize - 1) | (BootSect.BytPerSect - 1)) + 1; // round up to full sectors 161 | 162 | ClusterSize = BootSect.SectPerCluster * BootSect.BytPerSect; 163 | ClusterBase = 0x100 + BaseSects * BootSect.BytPerSect + RootDirSize - 2 * ClusterSize; // the first Cluster has number 2 164 | printf("Cluster Base:\t0x%04X\n", ClusterBase); 165 | printf("\n"); 166 | 167 | ReadFAT(0x100 + BootSect.ReservedSect * BootSect.BytPerSect); 168 | 169 | OutPath = (char*)malloc(strlen(argv[2]) + 2); 170 | strcpy(OutPath, argv[2]); 171 | StartPos = strlen(OutPath); 172 | if (StartPos && (OutPath[StartPos - 1] == '\\' || OutPath[StartPos - 1] == '/')) 173 | OutPath[StartPos - 1] = '\0'; 174 | _mkdir(OutPath); 175 | strcat(OutPath, "\\"); 176 | _getch(); 177 | ReadDirectory(0x100 + BaseSects * BootSect.BytPerSect, BootSect.RootDirEntries, OutPath, 0); 178 | 179 | free(OutPath); 180 | free(FATTbl); 181 | free(DimData); 182 | _getch(); 183 | 184 | return 0; 185 | } 186 | 187 | INLINE UINT16 ReadLE16(const UINT8* Data) 188 | { 189 | // read 16-Bit Word (Little Endian/Intel Byte Order) 190 | return (Data[0x01] << 8) | (Data[0x00] << 0); 191 | } 192 | 193 | INLINE UINT16 ReadBE16(const UINT8* Data) 194 | { 195 | // read 16-Bit Word (Big Endian/Motorola Byte Order) 196 | return (Data[0x00] << 8) | (Data[0x01] << 0); 197 | } 198 | 199 | static void ReadFAT(UINT32 BasePos) 200 | { 201 | UINT32 CurPos; 202 | UINT16 CurEnt; 203 | 204 | FATEntries = BootSect.SectPerFat * BootSect.BytPerSect / 3 * 2; // 12 bit per FAT entry (*8/12) 205 | FATTbl = (UINT16*)malloc(sizeof(UINT16) * FATEntries); 206 | for (CurEnt = 0x00, CurPos = BasePos; CurEnt < FATEntries; CurEnt += 2, CurPos += 0x03) 207 | { 208 | FATTbl[CurEnt + 0] = ( DimData[CurPos + 0] << 0) | 209 | ((DimData[CurPos + 1] & 0x0F) << 8); 210 | FATTbl[CurEnt + 1] = ((DimData[CurPos + 1] & 0xF0) >> 4) | 211 | ( DimData[CurPos + 2] << 4); 212 | } 213 | 214 | return; 215 | } 216 | 217 | static void ReadDirectory(UINT32 Cluster, UINT16 NumEntries, const char* BasePath, UINT8 Layer) 218 | { 219 | UINT32 DirBasePos; 220 | UINT16 CurClst; 221 | UINT32 CurPos; 222 | UINT32 EndPos; 223 | FAT_ENTRY* CurEntry; 224 | char* FileName; 225 | char* FileTitle; 226 | UINT8 TempByt; 227 | 228 | FileName = (char*)malloc(strlen(BasePath) + 16); // Base + "8.3" + '\0' -> Base+13 229 | strcpy(FileName, BasePath); 230 | FileTitle = FileName + strlen(FileName); 231 | 232 | if (NumEntries) 233 | { 234 | // Root Directory, Cluster == Base Offset 235 | CurClst = 0x00; 236 | DirBasePos = Cluster; 237 | EndPos = DirBasePos + NumEntries * 0x20; 238 | } 239 | else 240 | { 241 | CurClst = (UINT16)Cluster; 242 | } 243 | 244 | do 245 | { 246 | if (CurClst) // if NOT Root Directory 247 | { 248 | // another Directory 249 | DirBasePos = ClusterBase + ClusterSize * CurClst; 250 | EndPos = DirBasePos + ClusterSize; 251 | } 252 | 253 | for (CurPos = DirBasePos; CurPos < EndPos; CurPos += 0x20) 254 | { 255 | CurEntry = (FAT_ENTRY*)&DimData[CurPos]; 256 | if (CurEntry->Name[0] == 0x00) 257 | { 258 | CurClst = 0x00; // terminate instantly 259 | break; 260 | } 261 | 262 | BuildFilename(FileTitle, CurEntry); 263 | 264 | TempByt = Layer; 265 | while(TempByt --) 266 | putchar('\t'); 267 | 268 | if ((CurEntry->Attribute & 0x10)) // Is Directory? 269 | { 270 | // "." and ".." are for changing directories 271 | if (strcmp(FileTitle, ".") && strcmp(FileTitle, "..")) 272 | { 273 | _mkdir(FileName); 274 | strcat(FileTitle, "\\"); 275 | printf("%s\n", FileTitle); 276 | ReadDirectory(CurEntry->StartCluster, 0x00, FileName, Layer + 1); 277 | } 278 | else 279 | { 280 | printf("%s\n", FileTitle); 281 | } 282 | } 283 | else 284 | { 285 | printf("%s\n", FileTitle); 286 | ExtractFile(FileName, CurEntry->StartCluster, CurEntry->FileLength); 287 | } 288 | } 289 | CurClst = FATTbl[CurClst]; 290 | } while(CurClst && CurClst < 0xFF0); 291 | // Cluster 0x000 is the "free" cluster, so I use it as terminator. 292 | // Clusters 0xFF0 are EOF or invalid clusters, so I stop here, too. 293 | 294 | return; 295 | } 296 | 297 | static void BuildFilename(char* DestBuf, FAT_ENTRY* Entry) 298 | { 299 | UINT8 NameLen; 300 | UINT8 ExtLen; 301 | 302 | NameLen = 8; 303 | while(NameLen && Entry->Name[NameLen - 1] == ' ') 304 | NameLen --; 305 | 306 | ExtLen = 3; 307 | while(ExtLen && Entry->Extension[ExtLen - 1] == ' ') 308 | ExtLen --; 309 | 310 | if (ExtLen) 311 | sprintf(DestBuf, "%.*s.%.*s", NameLen, Entry->Name, ExtLen, Entry->Extension); 312 | else 313 | sprintf(DestBuf, "%.*s", NameLen, Entry->Name); 314 | 315 | return; 316 | } 317 | 318 | static void ExtractFile(const char* FileName, UINT16 Cluster, UINT32 FileSize) 319 | { 320 | FILE* hFile; 321 | UINT16 CurClst; 322 | UINT8* Buffer; 323 | UINT32 ClstPos; 324 | UINT32 WrtBytes; 325 | 326 | hFile = fopen(FileName, "wb"); 327 | if (hFile == NULL) 328 | return; 329 | 330 | Buffer = (UINT8*)malloc(ClusterSize); 331 | CurClst = Cluster; 332 | while(FileSize) 333 | { 334 | if (CurClst >= 0xFF0) 335 | break; 336 | WrtBytes = (FileSize > ClusterSize) ? ClusterSize : FileSize; 337 | ClstPos = ClusterBase + CurClst * ClusterSize; 338 | fwrite(&DimData[ClstPos], 0x01, WrtBytes, hFile); 339 | 340 | FileSize -= WrtBytes; 341 | CurClst = FATTbl[CurClst]; 342 | } 343 | 344 | fclose(hFile); 345 | 346 | return; 347 | } 348 | -------------------------------------------------------------------------------- /DiamondRushExtract.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef unsigned char UINT8; 6 | typedef unsigned int UINT32; 7 | 8 | typedef struct _file_toc 9 | { 10 | UINT32 Offset; 11 | UINT32 Length; 12 | } FILE_TOC; 13 | 14 | 15 | #define FCC_MTRK 0x6468544D 16 | #define FCC_PNG 0x474E5089 17 | 18 | #define BUFFER_SIZE 0x100 19 | UINT8 FileCount; 20 | FILE_TOC* Files; 21 | UINT32 HdrOffset; 22 | 23 | int main(int argc, char* argv[]) 24 | { 25 | FILE* hFileIn; 26 | FILE* hFileOut; 27 | UINT8 CurFile; 28 | UINT8 FileNumChrs; 29 | char* FileBase; 30 | char* OutName; 31 | const char* FileExt; 32 | char Buffer[BUFFER_SIZE]; 33 | UINT32 RemBytes; 34 | UINT32 WrtBytes; 35 | 36 | if (argc < 2) 37 | { 38 | printf("Usage: DRExtract.exe snd.f\n"); 39 | return 0; 40 | } 41 | 42 | hFileIn = fopen(argv[1], "rb"); 43 | if (hFileIn == NULL) 44 | { 45 | printf("Error opening file!\n"); 46 | return 1; 47 | } 48 | 49 | FileBase = (char*)malloc(strlen(argv[1]) + 1); 50 | strcpy(FileBase, argv[1]); 51 | OutName = strrchr(FileBase, '.'); 52 | if (OutName != NULL) 53 | *OutName = '\0'; 54 | 55 | OutName = (char*)malloc(strlen(FileBase) + 0x10); 56 | 57 | FileCount = (UINT8)fgetc(hFileIn); 58 | printf("%hu files found.\n", FileCount); 59 | if (FileCount <= 10) 60 | FileNumChrs = 1; 61 | else 62 | FileNumChrs = 2; 63 | 64 | Files = (FILE_TOC*)malloc(sizeof(FILE_TOC) * FileCount); 65 | fread(Files, sizeof(FILE_TOC), FileCount, hFileIn); 66 | 67 | HdrOffset = ftell(hFileIn); 68 | printf("Header Offset: 0x%04u\n", HdrOffset); 69 | 70 | for (CurFile = 0x00; CurFile < FileCount; CurFile ++) 71 | { 72 | fseek(hFileIn, HdrOffset + Files[CurFile].Offset, SEEK_SET); 73 | RemBytes = Files[CurFile].Length; 74 | 75 | fread(&WrtBytes, 0x04, 0x01, hFileIn); // read first 4 bytes 76 | fseek(hFileIn, -4, SEEK_CUR); 77 | 78 | switch(WrtBytes) // select file extention based on file header 79 | { 80 | case FCC_MTRK: 81 | FileExt = "mid"; 82 | break; 83 | case FCC_PNG: 84 | FileExt = "png"; 85 | break; 86 | default: 87 | FileExt = "bin"; 88 | break; 89 | } 90 | sprintf(OutName, "%s_%0*hu.%s", FileBase, FileNumChrs, CurFile, FileExt); 91 | printf("Extracting %s (%u bytes) ...", OutName, RemBytes); 92 | 93 | hFileOut = fopen(OutName, "wb"); 94 | if (hFileOut == NULL) 95 | { 96 | printf("Error opening file %s!\n", OutName); 97 | continue; 98 | } 99 | 100 | while(RemBytes) 101 | { 102 | WrtBytes = (RemBytes <= BUFFER_SIZE) ? RemBytes : BUFFER_SIZE; 103 | WrtBytes = fread(Buffer, 0x01, WrtBytes, hFileIn); 104 | if (! WrtBytes) 105 | { 106 | printf("Read error!\n"); 107 | break; 108 | } 109 | 110 | fwrite(Buffer, 0x01, WrtBytes, hFileOut); 111 | 112 | RemBytes -= WrtBytes; 113 | } 114 | 115 | fclose(hFileOut); 116 | 117 | printf("\n"); 118 | } 119 | 120 | fclose(hFileIn); 121 | printf("Done.\n"); 122 | 123 | free(FileBase); 124 | free(OutName); 125 | 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /FoxRangerExtract.c: -------------------------------------------------------------------------------- 1 | // Fox Ranger Music Extractor 2 | // -------------------------- 3 | // Valley Bell, written on 2021-10-31 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef HAVE_STDINT 9 | 10 | #include 11 | typedef uint8_t UINT8; 12 | typedef uint16_t UINT16; 13 | 14 | #else // ! HAVE_STDINT 15 | 16 | typedef unsigned char UINT8; 17 | typedef unsigned short UINT16; 18 | 19 | #endif // HAVE_STDINT 20 | 21 | 22 | static void ExtractArchive(size_t arcSize, const UINT8* arcData, size_t fileCnt, const char* fileName); 23 | static void DecryptData(size_t dataLen, UINT8* dst, const UINT8* src); 24 | static const char* GetFileTitle(const char* filePath); 25 | static const char* GetFileExtension(const char* filePath); 26 | static UINT16 ReadLE16(const UINT8* data); 27 | 28 | 29 | static UINT8 decodeKey = 0x6B; 30 | static size_t songCnt = 20; 31 | 32 | int main(int argc, char* argv[]) 33 | { 34 | int argbase; 35 | FILE* hFile; 36 | size_t inLen; 37 | UINT8* inData; 38 | 39 | printf("Fox Ranger Music Extractor\n--------------------------\n"); 40 | if (argc < 2) 41 | { 42 | printf("Usage: %s [options] archive.dat out.mid\n"); 43 | printf("This will generate out00.mid, out01.mid, etc.\n"); 44 | printf("\n"); 45 | printf("Options:\n"); 46 | printf(" -k specify XOR decode key (default: 0x%02X)\n", decodeKey); 47 | printf(" -n set number of songs in the file (default: %u)\n", (unsigned int)songCnt); 48 | return 0; 49 | } 50 | argbase = 1; 51 | while(argbase < argc && argv[argbase][0] == '-') 52 | { 53 | if (argv[argbase][1] == 'k') 54 | { 55 | argbase ++; 56 | if (argbase < argc) 57 | decodeKey = (UINT8)strtoul(argv[argbase], NULL, 0); 58 | } 59 | else if (argv[argbase][1] == 'n') 60 | { 61 | argbase ++; 62 | if (argbase < argc) 63 | songCnt = (size_t)strtoul(argv[argbase], NULL, 0); 64 | } 65 | else 66 | break; 67 | argbase ++; 68 | } 69 | if (argc < argbase + 2) 70 | { 71 | printf("Insufficient parameters!\n"); 72 | return 0; 73 | } 74 | 75 | hFile = fopen(argv[argbase + 0], "rb"); 76 | if (hFile == NULL) 77 | return 1; 78 | 79 | fseek(hFile, 0, SEEK_END); 80 | inLen = ftell(hFile); 81 | if (inLen > 0x1000000) 82 | inLen = 0x1000000; // limit to 16 MB 83 | 84 | inData = (UINT8*)malloc(inLen); 85 | fseek(hFile, 0, SEEK_SET); 86 | fread(inData, 0x01, inLen, hFile); 87 | 88 | fclose(hFile); 89 | 90 | ExtractArchive(inLen, inData, songCnt, argv[argbase + 1]); 91 | 92 | free(inData); 93 | 94 | return 0; 95 | } 96 | 97 | static void ExtractArchive(size_t arcSize, const UINT8* arcData, size_t fileCnt, const char* fileName) 98 | { 99 | const char* fileExt; 100 | char* outName; 101 | char* outExt; 102 | FILE* hFile; 103 | size_t tocPos; 104 | size_t filePos; 105 | size_t fileSize; 106 | size_t curFile; 107 | UINT8* decBuf; 108 | 109 | fileExt = GetFileExtension(fileName); 110 | if (fileExt == NULL) 111 | fileExt = fileName + strlen(fileName); 112 | outName = (char*)malloc(strlen(fileName) + 0x10); 113 | strcpy(outName, fileName); 114 | outExt = outName + (fileExt - fileName); 115 | 116 | // extract everything 117 | filePos = fileCnt * 0x02; 118 | tocPos = 0x00; 119 | decBuf = NULL; 120 | for (curFile = 0; curFile < fileCnt; curFile ++, tocPos += 0x02) 121 | { 122 | fileSize = ReadLE16(&arcData[tocPos]); 123 | 124 | // generate file name(ABC.ext -> ABC00.ext) 125 | if (fileCnt > 1) 126 | sprintf(outExt, "%02u%s", curFile, fileExt); 127 | 128 | printf("File %u / %u: offset: 0x%06X, size 0x%04X\n", 1 + curFile, fileCnt, filePos, fileSize); 129 | decBuf = (UINT8*)realloc(decBuf, fileSize); 130 | if (decBuf == NULL) 131 | { 132 | printf("Memory allocation failed!\n"); 133 | break; 134 | } 135 | DecryptData(fileSize, decBuf, &arcData[filePos]); 136 | filePos += fileSize; 137 | 138 | hFile = fopen(outName, "wb"); 139 | if (hFile == NULL) 140 | { 141 | printf("Error writing %s!\n", outName); 142 | continue; 143 | } 144 | fwrite(decBuf, 1, fileSize, hFile); 145 | fclose(hFile); 146 | } 147 | free(decBuf); 148 | 149 | return; 150 | } 151 | 152 | static void DecryptData(size_t dataLen, UINT8* dst, const UINT8* src) 153 | { 154 | size_t curPos; 155 | for (curPos = 0x00; curPos < dataLen; curPos ++) 156 | dst[curPos] = src[curPos] ^ decodeKey; 157 | return; 158 | } 159 | 160 | static const char* GetFileTitle(const char* filePath) 161 | { 162 | const char* sepPos1 = strrchr(filePath, '/'); 163 | const char* sepPos2 = strrchr(filePath, '\\'); 164 | const char* dirSepPos; 165 | 166 | if (sepPos1 == NULL) 167 | dirSepPos = sepPos2; 168 | else if (sepPos2 == NULL) 169 | dirSepPos = sepPos1; 170 | else 171 | dirSepPos = (sepPos1 < sepPos2) ? sepPos2 : sepPos1; 172 | return (dirSepPos != NULL) ? &dirSepPos[1] : filePath; 173 | } 174 | 175 | static const char* GetFileExtension(const char* filePath) 176 | { 177 | const char* fileTitle = GetFileTitle(filePath); 178 | const char* extDotPos = strrchr(fileTitle, '.'); 179 | return extDotPos; 180 | } 181 | 182 | static UINT16 ReadLE16(const UINT8* data) 183 | { 184 | return (data[0x00] << 0) | (data[0x01] << 8); 185 | } 186 | -------------------------------------------------------------------------------- /LBXUnpack.c: -------------------------------------------------------------------------------- 1 | // LBX Unpacker for Princess Maker 2 2 | // ------------ 3 | // Written by Valley Bell, 26 February 2013 4 | 5 | #include 6 | #include 7 | #include 8 | #include // for mkdir 9 | 10 | 11 | // Type Definitions for short types 12 | typedef unsigned char UINT8; 13 | typedef signed char INT8; 14 | 15 | typedef unsigned short UINT16; 16 | typedef signed short INT16; 17 | 18 | typedef unsigned int UINT32; 19 | typedef signed int INT32; 20 | 21 | 22 | #define printerr(x) fprintf(stderr, x) 23 | #define printerr2(x, y) fprintf(stderr, x, y) 24 | 25 | 26 | int main(int argc, char* argv[]); 27 | UINT8 UnpackLBXArchive(const char* InputFile, const char* ExtractPath); 28 | static void CreatePath(const char* FileName); 29 | static char* strchr_dir(const char* String); 30 | static void RTrimSpaces(char* String); 31 | static void PrintPMDIBMTags(const UINT32 FileSize, const UINT8* FileData); 32 | 33 | 34 | typedef struct lbx_toc 35 | { 36 | char Name[0x0C]; 37 | UINT32 Position; 38 | UINT32 Size; 39 | } LBX_TOC; 40 | 41 | 42 | int main(int argc, char* argv[]) 43 | { 44 | UINT8 RetVal; 45 | 46 | printf("LBX Unpacker Unpacker\n---------------------\n"); 47 | 48 | if (argc < 3) 49 | { 50 | printf("Usage: archive.lbx destpath/\n"); 51 | return 1; 52 | } 53 | 54 | RetVal = UnpackLBXArchive(argv[1], argv[2]); 55 | getchar(); 56 | 57 | return RetVal >> 3; 58 | } 59 | 60 | UINT8 UnpackLBXArchive(const char* InputFile, const char* ExtractPath) 61 | { 62 | UINT16 FileCount; 63 | UINT32 TOCPos; 64 | LBX_TOC* Files; 65 | UINT32 CurFile; 66 | FILE* hFile; 67 | FILE* hFileOut; 68 | UINT32 TempLng; 69 | LBX_TOC* TempFile; 70 | UINT8* FileBuf; 71 | char* FileName; 72 | char* FileNameTitle; 73 | 74 | hFile = fopen(InputFile, "rb"); 75 | if (hFile == NULL) 76 | { 77 | printerr("Error opening file!\n"); 78 | return 0x10; 79 | } 80 | 81 | fseek(hFile, -0x06, SEEK_END); 82 | 83 | TempLng = ftell(hFile); // get TOC end offset 84 | fread(&FileCount, 0x02, 0x01, hFile); 85 | fread(&TOCPos, 0x04, 0x01, hFile); 86 | 87 | if (TOCPos + FileCount * 0x14 > TempLng) 88 | { 89 | fclose(hFile); 90 | printerr("TOC too large! File invalid!\n"); 91 | return 0x20; 92 | } 93 | 94 | printf("LBX contains %u files.\n", FileCount); 95 | 96 | Files = (LBX_TOC*)malloc(FileCount * sizeof(LBX_TOC)); 97 | printf("Reading TOC ..."); 98 | fseek(hFile, TOCPos, SEEK_SET); 99 | TempLng = fread(Files, 0x14, FileCount, hFile); 100 | if (TempLng < FileCount) 101 | { 102 | printerr2("Warning: Could read only %u TOC entries!", TempLng); 103 | FileCount = TempLng; 104 | } 105 | printf(" OK\n"); 106 | 107 | printf("Extracting Files ...\n"); 108 | FileName = (char*)malloc(strlen(ExtractPath) + 0x10); 109 | strcpy(FileName, ExtractPath); 110 | FileNameTitle = FileName + strlen(FileName); 111 | 112 | for (CurFile = 0x00; CurFile < FileCount; CurFile ++) 113 | { 114 | TempFile = &Files[CurFile]; 115 | 116 | strncpy(FileNameTitle, TempFile->Name, 0x0C); 117 | FileNameTitle[0x0C] = '\0'; 118 | RTrimSpaces(FileNameTitle); 119 | 120 | CreatePath(FileName); 121 | hFileOut = fopen(FileName, "wb"); 122 | if (hFileOut == NULL) 123 | { 124 | printf("Error: Can't open %s!\n", FileNameTitle); 125 | } 126 | else 127 | { 128 | printf("%.12s\n", TempFile->Name); 129 | FileBuf = (UINT8*)malloc(TempFile->Size); 130 | 131 | fseek(hFile, TempFile->Position, SEEK_SET); 132 | fread(FileBuf, 0x01, TempFile->Size, hFile); 133 | fwrite(FileBuf, 0x01, TempFile->Size, hFileOut); 134 | 135 | if (FileBuf[0] == 0x02 && FileBuf[1] == 0x1A && FileBuf[2] == 0x00) 136 | PrintPMDIBMTags(TempFile->Size, FileBuf); 137 | 138 | free(FileBuf); FileBuf = NULL; 139 | fclose(hFileOut); hFileOut = NULL; 140 | } 141 | } 142 | printf("Done.\n"); 143 | free(FileName); 144 | 145 | return 0x00; 146 | } 147 | 148 | static void CreatePath(const char* FileName) 149 | { 150 | char* Path; 151 | char ChrBak; 152 | char* TempStr; 153 | 154 | Path = (char*)malloc(strlen(FileName) + 0x01); 155 | strcpy(Path, FileName); 156 | TempStr = strchr_dir(Path); 157 | while(TempStr != NULL) 158 | { 159 | ChrBak = *TempStr; 160 | *TempStr = 0x00; 161 | mkdir(Path); 162 | *TempStr = ChrBak; 163 | TempStr ++; 164 | 165 | TempStr = strchr_dir(TempStr); 166 | } 167 | 168 | return; 169 | } 170 | 171 | static char* strchr_dir(const char* String) 172 | { 173 | while(*String != '\0') 174 | { 175 | if (*String == '/' || *String == '\\') 176 | return (char*)String; 177 | String ++; 178 | } 179 | 180 | return NULL; 181 | } 182 | 183 | 184 | static void RTrimSpaces(char* String) 185 | { 186 | char* CurChr; 187 | 188 | CurChr = String; 189 | while(*CurChr != '\0') 190 | CurChr ++; 191 | CurChr --; 192 | 193 | while(*CurChr == ' ' && CurChr >= String) 194 | { 195 | *CurChr = '\0'; 196 | CurChr --; 197 | } 198 | 199 | return; 200 | } 201 | 202 | static void PrintPMDIBMTags(const UINT32 FileSize, const UINT8* FileData) 203 | { 204 | UINT16 TagOffsets[6]; 205 | const char* DataPtr; 206 | UINT8 CurTag; 207 | 208 | memcpy(TagOffsets, &FileData[FileSize - 0x0E], 0x0C); 209 | DataPtr = (const char*)FileData + 1; 210 | 211 | for (CurTag = 0; CurTag < 6; CurTag ++) 212 | { 213 | if (TagOffsets[CurTag] >= FileSize) 214 | continue; 215 | printf("\tTag %u:\t%s\n", CurTag, DataPtr + TagOffsets[CurTag]); 216 | } 217 | 218 | return; 219 | } 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extractors and Decoders 2 | 3 | This repository contains Various tools to extract and decompress game archives I wrote over the years. 4 | In most cases the goal was to extract game music. 5 | 6 | A CMake project file is included that allows you to quickly build all tools. 7 | The general compilation process is: 8 | 9 | - `mkdir build` 10 | - `cd build` 11 | - compiling with GCC: 12 | - `cmake .. -DCMAKE_BUILD_TYPE=Release` 13 | - `cmake --build .` 14 | - compiling with MS Visual C++: 15 | - `cmake ..` 16 | - `cmake --build . --config Release` 17 | 18 | ## CompileMLKTool 19 | 20 | This tool extracts and creates `MLK` music archives used by various games developed by the Japanese game developer "Compile". 21 | 22 | I developed it to extract the music from "Comet Summoner", which is part of Compile's "DiscStation Vol. 20". It is confirmed to work with all games from that disk. 23 | 24 | Notes about the MIDI files: 25 | 26 | - Looping has to be enabled using a flag in the MLK archive. 27 | - Custom loop markers are supported. "Control Change 31" is the Loop Start marker. Channel and value do not matter. 28 | - For some reason, "Time Signature" and "Key Signature" meta events are treated as Loop Start markers as well. 29 | - SysEx messages don't seem to work. 30 | 31 | ## CompileWLKTool 32 | 33 | This tool extracts and creates `WLK` sound archives used by various games developed by the Japanese game developer "Compile". 34 | 35 | While writing the music tool for "Comet Summoner", I had a look at the `WLK` files as well and they looked simple, so I wrote a tool for them as well. 36 | 37 | The tool is confirmed to work with all games from Compile's "DiscStation Vol. 20". 38 | Sounds are extracted to WAV files (format is PCM, 8-bit/16-bit, mono), as well as an accompanying text file that includes the internal flags field, as well as original file names where present. 39 | 40 | There are two known variants of the `WLK` format: 41 | 42 | - v1 has no magic bytes at the beginning 43 | - v2 begins with "WLKF0200" and can optionally store the original file names and paths. "Comet Summoner" is one of the games that includes original file paths. 44 | 45 | ## danbidec 46 | 47 | This tool decrypts music files used by the Korean game developer "Danbi System". 48 | 49 | The files are encrypted by XORing each byte with the low 8 bits of the file position. 50 | 51 | It is confirmed to work with the `.D` and `.I` files from "GoGo!! Our Star" / "GoGo Uribyeol". 52 | 53 | ## DiamondRushExtract 54 | 55 | This tool extracts the `.f` archives from the J2ME game "Diamond Rush" developed by Gameloft. 56 | 57 | I just wanted to get the MIDIs. 58 | 59 | ## DIMUnpack 60 | 61 | This tool unpacks certain `.DIM` disk image files that I was unable to open with DiskExplorer. 62 | 63 | ## FoxRangerExtract 64 | 65 | This tool extracts music from the archives used by the Korean game developer Soft Action, which was responsible for the "Fox Ranger" series. 66 | 67 | The archives themselves are unencrypted, but the unpacked files may need an XOR decryption. 68 | 69 | ## gensqu\_dec 70 | 71 | This tool decompresses files from "Genocide Square" (FM Towns). 72 | 73 | Supported are: 74 | 75 | - `.ARD` files (archives with compressed files, use `-a` parameter) 76 | - `.CAR` files (single compressed files, use `-f` parameter) 77 | 78 | The game uses a custom variant of LZSS. 79 | 80 | TODO: support extracting archives with uncompressed files 81 | 82 | ## kenji\_dec 83 | 84 | This is a tool to extract and decompress archives used by the PC-98 adventure/VN engine that has the copyright notice "Programed by KENJI". 85 | 86 | The adventure engine was commonly used by games published by Birdy Soft, Discovery and Orange House. 87 | 88 | Files whose extension ends with a `1` usually contain only a single file. 89 | Files whose extension ends with a `2` usually are archives and contain multiple files. 90 | 91 | It was originally called "twinkle\_dec", because it was written to extract music from "Bunretsu Shugo Shin Twinkle Star". 92 | 93 | The compression is standard LZSS with a non-standard initialization for the dictionary, which is the same that Wolf Team uses. 94 | 95 | ## LBXUnpack 96 | 97 | This tool unpacks the `.LBX` files used by the DOS version of "Princess Maker 2". 98 | 99 | ## lzss-lib / lzss-tool 100 | 101 | This library and tool allow you to decompress and recompress LZSS-compressed data. 102 | 103 | Tool and libary allow to specify various compression parameters like: 104 | - the initial values of the LZSS dictionary 105 | - the bit order of the control characters 106 | - the format of the backward reference word 107 | 108 | The tool also allows you to specify a file header format using the additional parameters. This way simple LZSS-compressed containers can be supported as well. 109 | 110 | ## mrndec 111 | 112 | This tool decompresses archives used by the Korean game developer "Mirinae Software". 113 | 114 | It was confirmed to work with the .MUE files from their "The Day" series. 115 | 116 | ## piyo\_dec 117 | 118 | This is a tool for decrypting the PC-98 executables (both `COM` and `EXE`) from games by PANDA HOUSE. 119 | 120 | The encryption is easily identifyable by the string `PIYO`, which can be found offset 06h in COM files or within the last 120 bytes of an EXE file. 121 | It uses an XOR encryption with a key that changes based on the unencrypted data. 122 | 123 | I initially wrote it as "mfd\_dec" with the goal of decrypting the `MFD.COM` sound driver executable. 124 | 125 | The source code contains comments about the file structure of encrypted executables. 126 | 127 | ## rekiai\_dec 128 | 129 | This tool unpacks song archives used by the PC-98 game "Rekiai". 130 | 131 | The `.MF` files contain 132 | 133 | - the song title 134 | - SSG and OPN instruments 135 | - MsDrv v4 files for OPN, OPNA and SC-55 MIDI 136 | 137 | ## wolfteam\_dec 138 | 139 | This tool decompresses archives used in games developed by Wolf Team. 140 | 141 | The compression is standard LZSS with a non-standard initialization for the dictionary. 142 | 143 | ## x86k\_sps\_dec 144 | 145 | X68000 S.P.S. Archive Unpacker 146 | 147 | Confirmed to work with: 148 | 149 | - Daimakaimura (TEXTDAT*.SLD) 150 | - Street Fighter II: Champion Edition (FM.BLK, GM.BLK) 151 | - Super Street Fighter II: The New Challengers (FM.BLK, GM.BLK) 152 | 153 | Daimakaimura and SSF2 use mostly standard LZSS, but modified to use a BigEndian reference word and not requiring a 4 KB dictionary. (The extracted data is used as reference only.) 154 | 155 | Super Street Fighter II uses LZSS with modifications to how the reference word works. `SSF2_Compr.txt` contains a disassembly of its decompression code. 156 | 157 | ## xordec 158 | 159 | A simple tool that XORs the whole file with a user-specified key. 160 | 161 | Some Korean game developers (e.g. Soft Action) use a simple XOR to encrypt their files. 162 | -------------------------------------------------------------------------------- /SSF2_Compr.txt: -------------------------------------------------------------------------------- 1 | Code at 0919C8-091A30 [decompressed from SP2.X, offset 00083A] 2 | 3 | Registers: 4 | A0 - [input] compressed source data 5 | A1 - [input] buffer for decompressed data 6 | D0 - remaining bits in "flags" value 7 | D1 - "flags" value 8 | D2 - data copy: read offset 9 | D3 - data copy: byte count 10 | SSF2_Decompress: 11 | 0919C8 moveq #0, d0 12 | 0919CA bra.s $0919CE 13 | 0919CC move.b (a0)+, (a1)+ 14 | 15 | 0919CE dbra d0, $0919D6 16 | 0919D2 addq.w #8, d0 17 | 0919D4 move.b (a0)+, d1 18 | 0919D6 add.b d1, d1 19 | 0919D8 bcs.s $0919CC ; copy single byte from source -> destination 20 | 0919DA moveq #-1, d2 21 | 0919DC dbra d0, $0919E4 22 | 0919E0 addq.w #8, d0 23 | 0919E2 move.b (a0)+, d1 24 | 0919E4 add.b d1, d1 25 | 0919E6 bcs.s $091A06 26 | 27 | 0919E8 move.b (a0)+, d2 28 | 0919EA moveq #7, d3 29 | 0919EC and.b d2, d3 30 | 0919EE bne.s $0919F6 31 | 0919F0 move.b (a0)+, d3 32 | 0919F2 beq.s $091A30 33 | 0919F4 subq.w #1, d3 34 | 0919F6 lsl.w #5, d2 35 | 0919F8 move.b (a0)+, d2 36 | 0919FA lea (a1,d2.w), a2 37 | 0919FE move.b (a2)+, (a1)+ 38 | 091A00 dbra d3, $0919FE 39 | 091A04 bra.s $0919CE 40 | 41 | 091A06 moveq #0, d3 42 | 091A08 dbra d0, $091A10 43 | 091A0C addq.w #8, d0 44 | 091A0E move.b (a0)+, d1 45 | 091A10 add.b d1, d1 46 | 091A12 addx.w d3, d3 47 | 091A14 dbra d0, $091A1A 48 | 091A18 addq.w #8, d0 49 | 091A1A move.b (a0)+, d1 50 | 091A1C add.b d1, d1 51 | 091A1E addx.w d3, d3 52 | 091A20 dbra d0, $091A28 53 | 091A24 addq.w #8, d0 54 | 091A26 move.b (a0)+, d1 55 | 091A28 add.b d1, d1 56 | 091A2A addx.w d3, d3 57 | 091A2C addq.w #1, d3 58 | 091A2E bra $0919F8 59 | 60 | 091A30 rts 61 | -------------------------------------------------------------------------------- /danbidec.c: -------------------------------------------------------------------------------- 1 | // Danbi System Decoder 2 | // -------------------- 3 | // Valley Bell, written on 2021-12-24 4 | #include 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | #include 9 | #endif 10 | 11 | typedef unsigned char UINT8; 12 | 13 | 14 | int main(int argc, char* argv[]) 15 | { 16 | int argbase; 17 | FILE* hFile; 18 | size_t inLen; 19 | UINT8* inData; 20 | #ifdef _WIN32 21 | FILETIME ftWrite; 22 | HANDLE hWinFile; 23 | #endif 24 | 25 | printf("Danbi System Decoder\n--------------------\n"); 26 | if (argc < 3) 27 | { 28 | printf("Usage: %s input.bin output.bin\n", argv[0]); 29 | return 0; 30 | } 31 | argbase = 1; 32 | /*while(argbase < argc && argv[argbase][0] == '-') 33 | { 34 | if (argv[argbase][1] == '\0') 35 | { 36 | } 37 | else 38 | break; 39 | argbase ++; 40 | }*/ 41 | if (argc < argbase + 2) 42 | { 43 | printf("Insufficient parameters!\n"); 44 | return 0; 45 | } 46 | 47 | hFile = fopen(argv[argbase + 0], "rb"); 48 | if (hFile == NULL) 49 | return 1; 50 | 51 | fseek(hFile, 0, SEEK_END); 52 | inLen = ftell(hFile); 53 | if (inLen > 0x1000000) 54 | inLen = 0x1000000; // limit to 16 MB 55 | 56 | inData = (UINT8*)malloc(inLen); 57 | fseek(hFile, 0, SEEK_SET); 58 | fread(inData, 0x01, inLen, hFile); 59 | 60 | fclose(hFile); 61 | 62 | #ifdef _WIN32 63 | ftWrite.dwLowDateTime = ftWrite.dwHighDateTime = 0; 64 | hWinFile = CreateFile(argv[argbase + 0], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 65 | if (hWinFile != INVALID_HANDLE_VALUE) 66 | { 67 | GetFileTime(hWinFile, NULL, NULL, &ftWrite); 68 | CloseHandle(hWinFile); 69 | } 70 | #endif 71 | 72 | { 73 | size_t curPos; 74 | for (curPos = 0x00; curPos < inLen; curPos ++) 75 | inData[curPos] ^= (curPos & 0xFF); 76 | 77 | hFile = fopen(argv[argbase + 1], "wb"); 78 | if (hFile == NULL) 79 | { 80 | printf("Error writing %s!\n", argv[argbase + 1]); 81 | } 82 | else 83 | { 84 | fwrite(inData, 1, inLen, hFile); 85 | fclose(hFile); 86 | } 87 | } 88 | 89 | #ifdef _WIN32 90 | if (ftWrite.dwLowDateTime != 0 && ftWrite.dwHighDateTime != 0) 91 | { 92 | hWinFile = CreateFile(argv[argbase + 1], GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 93 | if (hWinFile != INVALID_HANDLE_VALUE) 94 | { 95 | SetFileTime(hWinFile, NULL, NULL, &ftWrite); 96 | CloseHandle(hWinFile); 97 | } 98 | } 99 | #endif 100 | 101 | free(inData); 102 | 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /gensqu_dec.c: -------------------------------------------------------------------------------- 1 | // Genocide Square Decompressor 2 | // ---------------------------- 3 | // Valley Bell, written on 2018-05-25 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stdtype.h" 9 | 10 | 11 | static void DecompressFile(UINT32 inLen, const UINT8* inData, const char* fileName); 12 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 13 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData); 14 | static UINT16 ReadLE16(const UINT8* Data); 15 | static UINT32 ReadLE32(const UINT8* Data); 16 | 17 | 18 | int main(int argc, char* argv[]) 19 | { 20 | int argbase; 21 | FILE* hFile; 22 | UINT32 inLen; 23 | UINT8* inData; 24 | UINT8 fileFmt; 25 | 26 | printf("Genocide Square Decompressor\n----------------------------\n"); 27 | if (argc < 3) 28 | { 29 | printf("Usage: gensqu_dec.exe [Options] archive.ard output.bin\n"); 30 | printf("Options:\n"); 31 | printf(" -f single file\n"); 32 | printf(" -a archive (.ard, default)\n"); 33 | printf(" Note: File names are generated using the output name.\n"); 34 | printf(" Example: output.bin -> output_00.bin, output_01.bin, etc.\n"); 35 | printf("Supported/verified games: Bunretsu Shugo Shin Twinkle Star\n"); 36 | return 0; 37 | } 38 | 39 | fileFmt = 0; 40 | argbase = 1; 41 | while(argbase < argc && argv[argbase][0] == '-') 42 | { 43 | if (argv[argbase][1] == 'a') 44 | fileFmt = 0; 45 | else if (argv[argbase][1] == 'f') 46 | fileFmt = 1; 47 | else 48 | break; 49 | argbase ++; 50 | } 51 | if (argc < argbase + 2) 52 | { 53 | printf("Insufficient parameters!\n"); 54 | return 0; 55 | } 56 | 57 | hFile = fopen(argv[argbase + 0], "rb"); 58 | if (hFile == NULL) 59 | return 1; 60 | 61 | fseek(hFile, 0, SEEK_END); 62 | inLen = ftell(hFile); 63 | if (inLen > 0x1000000) 64 | inLen = 0x1000000; // limit to 16 MB 65 | 66 | inData = (UINT8*)malloc(inLen); 67 | fseek(hFile, 0, SEEK_SET); 68 | fread(inData, 0x01, inLen, hFile); 69 | 70 | fclose(hFile); 71 | 72 | switch(fileFmt) 73 | { 74 | case 0: 75 | DecompressArchive(inLen, inData, argv[argbase + 1]); 76 | break; 77 | case 1: 78 | DecompressFile(inLen, inData, argv[argbase + 1]); 79 | break; 80 | default: 81 | printf("Unknown format!\n"); 82 | break; 83 | } 84 | 85 | free(inData); 86 | 87 | return 0; 88 | } 89 | 90 | static void DecompressFile(UINT32 inLen, const UINT8* inData, const char* fileName) 91 | { 92 | UINT32 decSize; 93 | UINT8* decBuffer; 94 | UINT32 outSize; 95 | FILE* hFile; 96 | 97 | decSize = ReadLE32(&inData[0x00]); 98 | printf("Compressed: %u bytes, decompressed: %u bytes\n", inLen, decSize); 99 | decBuffer = (UINT8*)malloc(decSize); 100 | outSize = LZSS_Decode(inLen, &inData[0x04], decSize, decBuffer); 101 | if (outSize != decSize) 102 | printf("Warning - not all data was decompressed!\n"); 103 | 104 | hFile = fopen(fileName, "wb"); 105 | if (hFile == NULL) 106 | { 107 | free(decBuffer); 108 | printf("Error writing %s!\n", fileName); 109 | return; 110 | } 111 | fwrite(decBuffer, 1, decSize, hFile); 112 | fclose(hFile); 113 | free(decBuffer); 114 | 115 | return; 116 | } 117 | 118 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 119 | { 120 | const char* fileExt; 121 | char* outName; 122 | char* outExt; 123 | UINT32 filePos; 124 | UINT32 fileSize; 125 | UINT32 fileCnt; 126 | UINT32 curFile; 127 | UINT32 arcPos; 128 | UINT32 minPos; 129 | 130 | // detect number of files 131 | fileCnt = 0; 132 | minPos = arcSize; 133 | for (arcPos = 0x00; arcPos < minPos; arcPos += 0x04, fileCnt ++) 134 | { 135 | filePos = ReadLE32(&arcData[arcPos + 0x00]); 136 | if (! filePos) 137 | break; // the End-Of-TOC marker seems to be a file offset of 0 138 | if (filePos < minPos) 139 | minPos = filePos; 140 | } 141 | //printf("Detected %u %s.\n", fileCnt, (fileCnt == 1) ? "file" : "files"); 142 | 143 | fileExt = strrchr(fileName, '.'); 144 | if (fileExt == NULL) 145 | fileExt = fileName + strlen(fileName); 146 | outName = (char*)malloc(strlen(fileName) + 0x10); 147 | strcpy(outName, fileName); 148 | outExt = outName + (fileExt - fileName); 149 | 150 | // extract everything 151 | arcPos = 0x00; 152 | for (curFile = 0; curFile < fileCnt; curFile ++, arcPos += 0x04) 153 | { 154 | filePos = ReadLE32(&arcData[arcPos]); 155 | fileSize = ReadLE32(&arcData[arcPos + 0x04]); 156 | if (! fileSize) 157 | fileSize = arcSize; 158 | fileSize -= filePos; 159 | 160 | // generate file name(ABC.ext -> ABC_00.ext) 161 | sprintf(outExt, "_%02X%s", curFile, fileExt); 162 | 163 | printf("file %u / %u: offset: 0x%06X\n ", 1 + curFile, fileCnt, filePos); 164 | DecompressFile(fileSize, &arcData[filePos], outName); 165 | } 166 | 167 | return; 168 | } 169 | 170 | // custom LZSS variant used in Genocide Square (FM-Towns) 171 | // The decompression routine is stored at RAM offset 00600B5C. 172 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData) 173 | { 174 | // routine is loaded to offset 00600B5C 175 | UINT32 inPos, outPos; 176 | unsigned int i, j, k; 177 | unsigned int flags, fbits; 178 | 179 | flags = 0; fbits = 1; 180 | inPos = outPos = 0; 181 | while(inPos < inLen && outPos < outLen) { 182 | flags <<= 1; fbits --; 183 | if (!fbits) { 184 | flags = inData[inPos++]; 185 | fbits = 8; 186 | } 187 | if (flags & 0x80) { 188 | if (inPos >= inLen) break; 189 | outData[outPos++] = inData[inPos++]; 190 | } else { 191 | if (inPos + 1 >= inLen) break; 192 | // 00600BBF 193 | flags <<= 1; fbits --; 194 | if (!fbits) { 195 | flags = inData[inPos++]; 196 | fbits = 8; 197 | } 198 | if (flags & 0x80) 199 | { 200 | // 00600BF2 - duplicate last byte 201 | j = inData[inPos++]; 202 | if (j == 0) 203 | break; // data end 204 | for (k = 0; k <= j; k++) { 205 | if (outPos >= outLen) break; 206 | outData[outPos++] = outData[outPos - 1]; 207 | } 208 | } 209 | else 210 | { 211 | // 00600BC3 - copy previous section 212 | flags <<= 1; fbits --; 213 | if (!fbits) { 214 | flags = inData[inPos++]; 215 | fbits = 8; 216 | } 217 | j = (flags & 0x80) >> 6; 218 | flags <<= 1; fbits --; 219 | if (!fbits) { 220 | flags = inData[inPos++]; 221 | fbits = 8; 222 | } 223 | j |= (flags & 0x80) >> 7; 224 | if (j > 0) 225 | { 226 | // 00600BD2 227 | i = 0x100 - inData[inPos++]; 228 | } 229 | else 230 | { 231 | // 00600BE2 232 | i = ReadLE16(&inData[inPos]); inPos += 2; 233 | j = i & 0x0f; 234 | i = 0x1000 - (i >> 4); 235 | } 236 | for (k = 0; k <= j; k++) { 237 | if (outPos >= outLen) break; 238 | outData[outPos++] = outData[outPos - i]; 239 | } 240 | } 241 | } 242 | } 243 | return outPos; 244 | } 245 | 246 | static UINT16 ReadLE16(const UINT8* Data) 247 | { 248 | return (Data[0x00] << 0) | (Data[0x01] << 8); 249 | } 250 | 251 | static UINT32 ReadLE32(const UINT8* Data) 252 | { 253 | return (Data[0x00] << 0) | (Data[0x01] << 8) | 254 | (Data[0x02] << 16) | (Data[0x03] << 24); 255 | } 256 | -------------------------------------------------------------------------------- /kenji_dec.c: -------------------------------------------------------------------------------- 1 | // Kenji Decompressor 2 | // ------------------ 3 | // Valley Bell, written on 2017-03-27 / 2017-04-02 4 | // updated with fixed LZSS initialization on 2022-05-01 5 | #include 6 | #include 7 | #include 8 | 9 | #include "stdtype.h" 10 | 11 | 12 | static UINT8 DetectFileType(UINT32 fileSize, const UINT8* fileData, const char* fileName); 13 | static void DecompressFile(const UINT8* inData, const char* fileName); 14 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 15 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData); 16 | static UINT16 ReadLE16(const UINT8* Data); 17 | static UINT32 ReadLE32(const UINT8* Data); 18 | 19 | 20 | int main(int argc, char* argv[]) 21 | { 22 | int argbase; 23 | FILE* hFile; 24 | UINT32 inLen; 25 | UINT8* inData; 26 | UINT8 fileFmt; 27 | 28 | printf("Kenji Decompressor\n------------------\n"); 29 | if (argc < 3) 30 | { 31 | printf("Usage: kenji_dec.exe [Options] input.bin output.bin\n"); 32 | printf("Options:\n"); 33 | printf(" -0 single/archive autodetection\n"); 34 | printf(" -1 single file (.##1 extension)\n"); 35 | printf(" -2 archive (.##2 extension)\n"); 36 | printf(" Note: File names are generated using the output name.\n"); 37 | printf(" Example: output.bin -> output_00.bin, output_01.bin, etc.\n"); 38 | printf("for files and archives used by Kenji's adventure engine\n"); 39 | return 0; 40 | } 41 | 42 | fileFmt = 0; 43 | argbase = 1; 44 | while(argbase < argc && argv[argbase][0] == '-') 45 | { 46 | if (argv[argbase][1] >= '0' && argv[argbase][1] <= '2') 47 | { 48 | fileFmt = argv[argbase][1] - '0'; 49 | } 50 | else 51 | break; 52 | argbase ++; 53 | } 54 | if (argc < argbase + 2) 55 | { 56 | printf("Insufficient parameters!\n"); 57 | return 0; 58 | } 59 | 60 | hFile = fopen(argv[argbase + 0], "rb"); 61 | if (hFile == NULL) 62 | return 1; 63 | 64 | fseek(hFile, 0, SEEK_END); 65 | inLen = ftell(hFile); 66 | if (inLen > 0x100000) 67 | inLen = 0x100000; // limit to 1 MB 68 | 69 | inData = (UINT8*)malloc(inLen); 70 | fseek(hFile, 0, SEEK_SET); 71 | fread(inData, 0x01, inLen, hFile); 72 | 73 | fclose(hFile); 74 | 75 | if (fileFmt == 0) 76 | { 77 | fileFmt = DetectFileType(inLen, inData, argv[argbase + 0]); 78 | printf("Detected format: %u\n", fileFmt); 79 | } 80 | switch(fileFmt) 81 | { 82 | case 1: 83 | DecompressFile(inData, argv[argbase + 1]); 84 | break; 85 | case 2: 86 | DecompressArchive(inLen, inData, argv[argbase + 1]); 87 | break; 88 | default: 89 | printf("Unknown format!\n"); 90 | break; 91 | } 92 | 93 | free(inData); 94 | 95 | return 0; 96 | } 97 | 98 | static UINT8 DetectFileType(UINT32 fileSize, const UINT8* fileData, const char* fileName) 99 | { 100 | const char* tempPtr; 101 | UINT16 filePos; 102 | UINT16 fileType; 103 | UINT32 dummyData; 104 | UINT32 comprLen; 105 | 106 | if (fileSize < 0x08) 107 | return 0; 108 | 109 | tempPtr = strrchr(fileName, '.'); 110 | if (tempPtr != NULL) 111 | { 112 | tempPtr += strlen(tempPtr) - 1; // go to last character 113 | if (*tempPtr == '1') // .xx1 - single compressed file 114 | return 1; 115 | else if (*tempPtr == '2') // .xx2 - archive file 116 | return 2; 117 | } 118 | 119 | filePos = ReadLE16(&fileData[0x00]); 120 | fileType = ReadLE16(&fileData[0x02]); 121 | dummyData = ReadLE32(&fileData[0x04]); 122 | if (filePos < fileSize && fileType < 0x100 && ! dummyData) 123 | return 2; // this should be an archive 124 | 125 | comprLen = ReadLE32(&fileData[0x00]); 126 | if (comprLen <= fileSize - 0x08) 127 | return 1; // might be a single compressed file 128 | 129 | return 0; // detection failed 130 | } 131 | 132 | static void DecompressFile(const UINT8* inData, const char* fileName) 133 | { 134 | UINT32 comprSize; 135 | UINT32 decSize; 136 | UINT8* decBuffer; 137 | UINT32 outSize; 138 | FILE* hFile; 139 | 140 | comprSize = ReadLE32(&inData[0x00]); 141 | decSize = ReadLE32(&inData[0x04]); 142 | printf("Compressed: %u bytes, decompressed: %u bytes\n", comprSize, decSize); 143 | decBuffer = (UINT8*)malloc(decSize); 144 | outSize = LZSS_Decode(comprSize, &inData[0x08], decSize, decBuffer); 145 | if (outSize != decSize) 146 | printf("Warning - not all data was decompressed!\n"); 147 | 148 | hFile = fopen(fileName, "wb"); 149 | if (hFile == NULL) 150 | { 151 | printf("Error writing %s!\n", fileName); 152 | return; 153 | } 154 | fwrite(decBuffer, 1, decSize, hFile); 155 | fclose(hFile); 156 | 157 | return; 158 | } 159 | 160 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 161 | { 162 | const char* fileExt; 163 | char* outName; 164 | char* outExt; 165 | UINT16 filePos; 166 | UINT16 fileType; 167 | UINT32 fileCnt; 168 | UINT32 curFile; 169 | UINT16 arcPos; 170 | UINT16 minPos; 171 | 172 | // detect number of files 173 | fileCnt = 0; 174 | minPos = (arcSize <= 0xFFFF) ? arcSize : 0xFFFF; 175 | for (arcPos = 0x00; arcPos < minPos; arcPos += 0x08, fileCnt ++) 176 | { 177 | filePos = ReadLE16(&arcData[arcPos + 0x00]); 178 | if (filePos < minPos) 179 | minPos = filePos; 180 | fileType = ReadLE16(&arcData[arcPos + 0x02]); 181 | if (fileType >= 0x100) 182 | break; 183 | if (ReadLE32(&arcData[arcPos + 0x04])) 184 | break; 185 | } 186 | //printf("Detected %u %s.\n", fileCnt, (fileCnt == 1) ? "file" : "files"); 187 | 188 | fileExt = strrchr(fileName, '.'); 189 | if (fileExt == NULL) 190 | fileExt = fileName + strlen(fileName); 191 | outName = (char*)malloc(strlen(fileName) + 0x10); 192 | strcpy(outName, fileName); 193 | outExt = outName + (fileExt - fileName); 194 | 195 | // extract everything 196 | arcPos = 0x00; 197 | for (curFile = 0; curFile < fileCnt; curFile ++, arcPos += 0x08) 198 | { 199 | filePos = ReadLE16(&arcData[arcPos + 0x00]); 200 | fileType = ReadLE16(&arcData[arcPos + 0x02]); 201 | 202 | // generate file name(ABC.ext -> ABC_00.ext) 203 | sprintf(outExt, "_%02X%s", curFile, fileExt); 204 | 205 | printf("file %u / %u: type: %02X, offset: 0x%04X\n ", 1 + curFile, fileCnt, fileType, filePos); 206 | DecompressFile(&arcData[filePos], outName); 207 | } 208 | 209 | return; 210 | } 211 | 212 | // LZSS decoder by Haruhiko Okumura, 1989-04-06 213 | // modified to work with memory instead of file streams 214 | 215 | #define N 4096 /* size of ring buffer */ 216 | #define F 18 /* upper limit for match_length */ 217 | #define THRESHOLD 2 /* encode string into position and length 218 | if match_length is greater than this */ 219 | 220 | static void LZSS_BufInit(UINT8* text_buf) 221 | { 222 | // Important Note: These are non-standard values and ARE used by the compressed data. 223 | UINT16 bufPos; 224 | UINT16 regD0; 225 | UINT16 regD1; 226 | 227 | // LZSS table initialization, originally from Arcus Odyssey X68000, M_DRV.X 228 | // verified using TSTAR.EXE 229 | bufPos = 0x0000; 230 | // 000..CFF (0x0D bytes of 00, 01, 02, ... FF each) 231 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++) 232 | { 233 | for (regD1 = 0x00; regD1 < 0x0D; regD1 ++, bufPos ++) 234 | text_buf[bufPos] = (UINT8)regD0; 235 | } 236 | // AD00..ADFF (00 .. FF) 237 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++, bufPos ++) 238 | text_buf[bufPos] = (UINT8)regD0; 239 | // AE00..AEFF (FF .. 00) 240 | do 241 | { 242 | regD0 --; 243 | text_buf[bufPos] = (UINT8)regD0; 244 | bufPos ++; 245 | } while(regD0 > 0x00); 246 | // AF00..AF7F (0x80 times 00) 247 | for (regD0 = 0x00; regD0 < 0x80; regD0 ++, bufPos ++) 248 | text_buf[bufPos] = 0x00; 249 | // AF80..AFED (0x6E times 20/space) 250 | for (regD0 = 0x00; regD0 < 0x80 - F; regD0 ++, bufPos ++) 251 | text_buf[bufPos] = ' '; 252 | 253 | return; 254 | } 255 | 256 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData) 257 | { 258 | UINT32 inPos, outPos; 259 | UINT8 text_buf[N]; /* ring buffer of size N, 260 | with extra F-1 bytes to facilitate string comparison */ 261 | int i, j, k, r, c; 262 | unsigned int flags; 263 | 264 | LZSS_BufInit(text_buf); 265 | r = N - F; flags = 0; 266 | inPos = outPos = 0; 267 | while(inPos < inLen && outPos < outLen) { 268 | if (((flags >>= 1) & 256) == 0) { 269 | c = inData[inPos++]; 270 | flags = c | 0xff00; /* uses higher byte cleverly */ 271 | } /* to count eight */ 272 | if (flags & 1) { 273 | if (inPos >= inLen) break; 274 | c = inData[inPos++]; 275 | outData[outPos++] = c; text_buf[r++] = c; r &= (N - 1); 276 | } else { 277 | if (inPos + 1 >= inLen) break; 278 | i = inData[inPos++]; 279 | j = inData[inPos++]; 280 | i |= ((j & 0xf0) << 4); j = (j & 0x0f) + THRESHOLD; 281 | for (k = 0; k <= j; k++) { 282 | c = text_buf[(i + k) & (N - 1)]; 283 | if (outPos >= outLen) break; 284 | outData[outPos++] = c; text_buf[r++] = c; r &= (N - 1); 285 | } 286 | } 287 | } 288 | return outPos; 289 | } 290 | 291 | static UINT16 ReadLE16(const UINT8* Data) 292 | { 293 | return (Data[0x00] << 0) | (Data[0x01] << 8); 294 | } 295 | 296 | static UINT32 ReadLE32(const UINT8* Data) 297 | { 298 | return (Data[0x00] << 0) | (Data[0x01] << 8) | 299 | (Data[0x02] << 16) | (Data[0x03] << 24); 300 | } 301 | -------------------------------------------------------------------------------- /lzss-lib.c: -------------------------------------------------------------------------------- 1 | /* LZSS compression and decompression library 2 | based on LZSS.C by Haruhiko Okumura 3 | extended with various customizations by Valley Bell 4 | */ 5 | #include 6 | #include 7 | #include "lzss-lib.h" 8 | 9 | struct _lzss_compressor 10 | { 11 | LZSS_CFG cfg; 12 | 13 | unsigned int N; /* size of ring buffer, usually 4096 */ 14 | unsigned int F; /* upper limit for match_length, usually 18 */ 15 | unsigned int THRESHOLD; /* encode string into position and length 16 | if match_length is greater than this, usually 2 */ 17 | #define NIL N /* index for root of binary search trees */ 18 | 19 | uint8_t* text_buf; /* ring buffer of size N, with extra F-1 bytes to 20 | facilitate string comparison of longest match. 21 | These are set by the InsertNode() procedure. */ 22 | int match_position; 23 | unsigned int match_length; 24 | int* lson; /* left & right children & parents -- These constitute binary search trees. */ 25 | int* rson; 26 | int* dad; 27 | }; 28 | 29 | 30 | LZSS_COMPR* lzssCreate(const LZSS_CFG* config) 31 | { 32 | LZSS_COMPR* lzss = (LZSS_COMPR*)calloc(1, sizeof(LZSS_COMPR)); 33 | if (lzss == NULL) 34 | return NULL; 35 | 36 | lzss->cfg = *config; 37 | lzss->N = 4096; 38 | lzss->THRESHOLD = 2; 39 | lzss->F = 0x10 + lzss->THRESHOLD; 40 | lzss->text_buf = (uint8_t*)malloc(lzss->N + lzss->F - 1); 41 | 42 | lzss->lson = (int*)calloc(lzss->N + 1, sizeof(int)); 43 | lzss->rson = (int*)calloc(lzss->N + 0x101, sizeof(int)); 44 | lzss->dad = (int*)calloc(lzss->N + 1, sizeof(int)); 45 | return lzss; 46 | } 47 | 48 | void lzssDestroy(LZSS_COMPR* lzss) 49 | { 50 | free(lzss->text_buf); 51 | free(lzss->lson); 52 | free(lzss->rson); 53 | free(lzss->dad); 54 | free(lzss); 55 | } 56 | 57 | void lzssGetDefaultConfig(LZSS_CFG* config) 58 | { 59 | config->flags = LZSS_FLAGS_CTRL_L | LZSS_FLAGS_MTCH_DEFAULT; 60 | config->nameTblType = LZSS_NTINIT_VALUE; 61 | config->nameTblValue = ' '; // space 62 | config->nameTblFunc = NULL; 63 | config->ntFuncParam = NULL; 64 | config->nameTblStartOfs = LZSS_NTSTOFS_NF; 65 | config->eosMode = LZSS_EOSM_NONE; 66 | return; 67 | } 68 | 69 | const LZSS_CFG* lzssGetConfiguration(const LZSS_COMPR* lzss) 70 | { 71 | return &lzss->cfg; 72 | } 73 | 74 | static void InitTree(LZSS_COMPR* lzss) /* initialize trees */ 75 | { 76 | unsigned int i; 77 | 78 | /* For i = 0 to N - 1, rson[i] and lson[i] will be the right and 79 | left children of node i. These nodes need not be initialized. 80 | Also, dad[i] is the parent of node i. These are initialized to 81 | NIL (= N), which stands for 'not used.' 82 | For i = 0 to 255, rson[N + i + 1] is the root of the tree 83 | for strings that begin with character i. These are initialized 84 | to NIL. Note there are 256 trees. */ 85 | 86 | for (i = lzss->N + 1; i <= lzss->N + 256; i++) lzss->rson[i] = lzss->NIL; 87 | for (i = 0; i < lzss->N; i++) lzss->dad[i] = lzss->NIL; 88 | } 89 | 90 | static void InsertNode(LZSS_COMPR* lzss, int r) 91 | /* Inserts string of length F, text_buf[r..r+F-1], into one of the 92 | trees (text_buf[r]'th tree) and returns the longest-match position 93 | and length via the global variables match_position and match_length. 94 | If match_length = F, then removes the old node in favor of the new 95 | one, because the old one will be deleted sooner. 96 | Note r plays double role, as tree node and position in buffer. */ 97 | { 98 | unsigned int i; 99 | int p, cmp; 100 | uint8_t *key; 101 | 102 | cmp = 1; key = &lzss->text_buf[r]; p = lzss->N + 1 + key[0]; 103 | lzss->rson[r] = lzss->lson[r] = lzss->NIL; lzss->match_length = 0; 104 | for ( ; ; ) { 105 | if (cmp >= 0) { 106 | if (lzss->rson[p] != lzss->NIL) p = lzss->rson[p]; 107 | else { lzss->rson[p] = r; lzss->dad[r] = p; return; } 108 | } else { 109 | if (lzss->lson[p] != lzss->NIL) p = lzss->lson[p]; 110 | else { lzss->lson[p] = r; lzss->dad[r] = p; return; } 111 | } 112 | for (i = 1; i < lzss->F; i++) 113 | if ((cmp = key[i] - (int)lzss->text_buf[p + i]) != 0) break; 114 | if (i > lzss->match_length) { 115 | lzss->match_position = p; 116 | if ((lzss->match_length = i) >= lzss->F) break; 117 | } 118 | } 119 | lzss->dad[r] = lzss->dad[p]; lzss->lson[r] = lzss->lson[p]; lzss->rson[r] = lzss->rson[p]; 120 | lzss->dad[lzss->lson[p]] = r; lzss->dad[lzss->rson[p]] = r; 121 | if (lzss->rson[lzss->dad[p]] == p) lzss->rson[lzss->dad[p]] = r; 122 | else lzss->lson[lzss->dad[p]] = r; 123 | lzss->dad[p] = lzss->NIL; /* remove p */ 124 | } 125 | 126 | static void DeleteNode(LZSS_COMPR* lzss, int p) /* deletes node p from tree */ 127 | { 128 | int q; 129 | 130 | if (lzss->dad[p] == lzss->NIL) return; /* not in tree */ 131 | if (lzss->rson[p] == lzss->NIL) q = lzss->lson[p]; 132 | else if (lzss->lson[p] == lzss->NIL) q = lzss->rson[p]; 133 | else { 134 | q = lzss->lson[p]; 135 | if (lzss->rson[q] != lzss->NIL) { 136 | do { q = lzss->rson[q]; } while (lzss->rson[q] != lzss->NIL); 137 | lzss->rson[lzss->dad[q]] = lzss->lson[q]; lzss->dad[lzss->lson[q]] = lzss->dad[q]; 138 | lzss->lson[q] = lzss->lson[p]; lzss->dad[lzss->lson[p]] = q; 139 | } 140 | lzss->rson[q] = lzss->rson[p]; lzss->dad[lzss->rson[p]] = q; 141 | } 142 | lzss->dad[q] = lzss->dad[p]; 143 | if (lzss->rson[lzss->dad[p]] == p) lzss->rson[lzss->dad[p]] = q; else lzss->lson[lzss->dad[p]] = q; 144 | lzss->dad[p] = lzss->NIL; 145 | } 146 | 147 | static void InitNametable(LZSS_COMPR* lzss) 148 | { 149 | if (lzss->cfg.nameTblType == LZSS_NTINIT_VALUE) 150 | memset(lzss->text_buf, lzss->cfg.nameTblValue, lzss->N); 151 | else if (lzss->cfg.nameTblType == LZSS_NTINIT_FUNC) 152 | lzss->cfg.nameTblFunc(lzss, lzss->cfg.ntFuncParam, lzss->N, lzss->text_buf); 153 | else 154 | memset(lzss->text_buf, 0x00, lzss->N); 155 | } 156 | 157 | uint8_t lzssEncode(LZSS_COMPR* lzss, size_t bufSize, uint8_t* buffer, size_t* bytesWritten, size_t inSize, const uint8_t* inData) 158 | { 159 | size_t codesize = 0; /* code size counter */ 160 | 161 | unsigned int i, len, r, s, last_match_length, code_buf_ptr; 162 | unsigned int maskN = lzss->N - 1; 163 | uint8_t code_buf[17]; // control byte (1) + 8 reference words (16) 164 | uint8_t mask; 165 | size_t inPos; 166 | size_t outPos; 167 | 168 | if (inSize == 0) /* text of size zero */ 169 | { 170 | if (bytesWritten != NULL) *bytesWritten = 0; 171 | return LZSS_ERR_OK; 172 | } 173 | 174 | lzss->match_position = 0; 175 | lzss->match_length = 0; 176 | InitTree(lzss); /* initialize trees */ 177 | code_buf[0] = 0; /* code_buf[1..16] saves eight units of code, and 178 | code_buf[0] works as eight flags, "1" representing that the unit 179 | is an unencoded letter (1 byte), "0" a position-and-length pair 180 | (2 bytes). Thus, eight units require at most 16 bytes of code. */ 181 | code_buf_ptr = 1; 182 | mask = ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_L) ? 0x01 : 0x80; 183 | 184 | InitNametable(lzss); 185 | 186 | inPos = 0; 187 | outPos = 0; 188 | if (lzss->cfg.nameTblStartOfs == LZSS_NTSTOFS_NF) 189 | r = lzss->N - lzss->F; 190 | else 191 | r = lzss->cfg.nameTblStartOfs & maskN; 192 | s = (r + lzss->F) & maskN; 193 | for (len = 0; len < lzss->F && inPos < inSize; len++) 194 | { 195 | uint8_t c = inData[inPos++]; 196 | lzss->text_buf[(r + len) & maskN] = c; /* Read F bytes into the last F bytes of 197 | the buffer */ 198 | } 199 | memcpy(&lzss->text_buf[lzss->N], &lzss->text_buf[0], lzss->F - 1); // for easy string comparison 200 | if (lzss->cfg.nameTblType == LZSS_NTINIT_FUNC) 201 | { 202 | // build a tree so that the whole nametable is included 203 | for (i = 1; i <= lzss->N - lzss->F; i++) InsertNode(lzss, (r + lzss->N - i) & maskN); 204 | } 205 | else if (lzss->cfg.nameTblType != LZSS_NTINIT_NONE) 206 | { 207 | for (i = 1; i <= lzss->F; i++) InsertNode(lzss, (r + lzss->N - i) & maskN); /* Insert the F strings, 208 | each of which begins with one or more 'space' characters. Note 209 | the order in which these strings are inserted. This way, 210 | degenerate trees will be less likely to occur. */ 211 | } 212 | InsertNode(lzss, r); /* Finally, insert the whole string just read. The 213 | global variables match_length and match_position are set. */ 214 | do { 215 | if (lzss->match_length > len) lzss->match_length = len; /* match_length 216 | may be spuriously long near the end of text. */ 217 | if (lzss->match_length <= lzss->THRESHOLD) { 218 | lzss->match_length = 1; /* Not long enough match. Send one byte. */ 219 | code_buf[0] |= mask; /* 'send one byte' flag */ 220 | code_buf[code_buf_ptr++] = lzss->text_buf[r]; /* Send uncoded. */ 221 | } else { 222 | /* Send position and length pair. Note match_length > THRESHOLD. */ 223 | unsigned int mlen = lzss->match_length - (lzss->THRESHOLD + 1); 224 | uint8_t i, j; 225 | switch(lzss->cfg.flags & LZSS_FLAGS_MTCH_LMASK) 226 | { 227 | case LZSS_FLAGS_MTCH_L_HH: 228 | i = (uint8_t)lzss->match_position; 229 | j = (uint8_t)(((lzss->match_position >> 8) & 0x0f) | (mlen << 4)); 230 | break; 231 | case LZSS_FLAGS_MTCH_L_HL: 232 | default: 233 | i = (uint8_t)lzss->match_position; 234 | j = (uint8_t)(((lzss->match_position >> 4) & 0xf0) | mlen); 235 | break; 236 | case LZSS_FLAGS_MTCH_L_LH: 237 | i = (uint8_t)((lzss->match_position & 0x0f) | (mlen << 4)); 238 | j = (uint8_t)(lzss->match_position >> 4); 239 | break; 240 | case LZSS_FLAGS_MTCH_L_LL: 241 | i = (uint8_t)(((lzss->match_position << 4) & 0xf0) | mlen); 242 | j = (uint8_t)(lzss->match_position >> 4); 243 | break; 244 | } 245 | if ((lzss->cfg.flags & LZSS_FLAGS_MTCH_EMASK) == LZSS_FLAGS_MTCH_ELITTLE) 246 | { 247 | code_buf[code_buf_ptr++] = i; 248 | code_buf[code_buf_ptr++] = j; 249 | } 250 | else //if ((lzss->cfg.flags & LZSS_FLAGS_MTCH_EMASK) == LZSS_FLAGS_MTCH_EBIG) 251 | { 252 | code_buf[code_buf_ptr++] = j; 253 | code_buf[code_buf_ptr++] = i; 254 | } 255 | } 256 | if ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_L) 257 | mask <<= 1; // shift left one bit (low -> high) 258 | else //if ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_H) 259 | mask >>= 1; // shift right one bit (high -> low) 260 | if (mask == 0) { 261 | for (i = 0; i < code_buf_ptr; i++) /* Send at most 8 units of */ 262 | { 263 | if (outPos >= bufSize) 264 | { 265 | if (bytesWritten != NULL) *bytesWritten = outPos; 266 | return LZSS_ERR_EOF_OUT; 267 | } 268 | buffer[outPos++] = code_buf[i]; /* code together */ 269 | } 270 | codesize += code_buf_ptr; 271 | code_buf[0] = 0; code_buf_ptr = 1; 272 | mask = ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_L) ? 0x01 : 0x80; 273 | } 274 | last_match_length = lzss->match_length; 275 | for (i = 0; i < last_match_length; i++) { 276 | uint8_t c; 277 | if (inPos >= inSize) 278 | break; 279 | DeleteNode(lzss, s); /* Delete old strings and */ 280 | c = inData[inPos++]; 281 | lzss->text_buf[s] = c; /* read new bytes */ 282 | if (s < lzss->F - 1) lzss->text_buf[s + lzss->N] = c; /* If the position is 283 | near the end of buffer, extend the buffer to make 284 | string comparison easier. */ 285 | s = (s + 1) & maskN; r = (r + 1) & maskN; 286 | /* Since this is a ring buffer, increment the position 287 | modulo N. */ 288 | InsertNode(lzss, r); /* Register the string in text_buf[r..r+F-1] */ 289 | } 290 | while (i++ < last_match_length) { /* After the end of text, */ 291 | DeleteNode(lzss, s); /* no need to read, but */ 292 | s = (s + 1) & maskN; r = (r + 1) & maskN; 293 | if (--len) InsertNode(lzss, r); /* buffer may not be empty. */ 294 | } 295 | } while (len > 0); /* until length of string to be processed is zero */ 296 | if (code_buf_ptr > 1) { /* Send remaining code. */ 297 | for (i = 0; i < code_buf_ptr; i++) 298 | { 299 | if (outPos >= bufSize) 300 | { 301 | if (bytesWritten != NULL) *bytesWritten = outPos; 302 | return LZSS_ERR_EOF_OUT; 303 | } 304 | buffer[outPos++] = code_buf[i]; 305 | } 306 | codesize += code_buf_ptr; 307 | } 308 | if (lzss->cfg.eosMode == LZSS_EOSM_REF0) 309 | { 310 | // add null-reference 311 | if (code_buf_ptr > 1) 312 | { 313 | i = 1; // just write the terminating reference word 314 | } 315 | else 316 | { 317 | code_buf[0] = 0; // "reference flag" control byte 318 | i = 0; // write control byte + reference word 319 | } 320 | code_buf[1] = code_buf[2] = 0; // reference word 321 | code_buf_ptr = 3; 322 | for (; i < code_buf_ptr; i++) 323 | { 324 | if (outPos >= bufSize) 325 | { 326 | if (bytesWritten != NULL) *bytesWritten = outPos; 327 | return LZSS_ERR_EOF_OUT; 328 | } 329 | buffer[outPos++] = code_buf[i]; 330 | } 331 | } 332 | 333 | if (bytesWritten != NULL) *bytesWritten = outPos; 334 | return LZSS_ERR_OK; 335 | } 336 | 337 | uint8_t lzssDecode(LZSS_COMPR* lzss, size_t bufSize, uint8_t* buffer, size_t* bytesWritten, size_t inSize, const uint8_t* inData) 338 | { 339 | unsigned int maskN = lzss->N - 1; 340 | unsigned int r; // ring buffer position 341 | uint8_t flags; 342 | unsigned int flag_bits; 343 | size_t inPos; 344 | size_t outPos; 345 | 346 | InitNametable(lzss); 347 | 348 | if (lzss->cfg.nameTblStartOfs == LZSS_NTSTOFS_NF) 349 | r = lzss->N - lzss->F; 350 | else 351 | r = lzss->cfg.nameTblStartOfs & maskN; 352 | flags = 0; 353 | flag_bits = 0; 354 | inPos = 0; 355 | outPos = 0; 356 | while(1) 357 | { 358 | unsigned int lz_flag; 359 | 360 | if (flag_bits == 0) 361 | { 362 | if (inPos >= inSize) 363 | break; // EOF is valid here 364 | flags = inData[inPos++]; 365 | flag_bits = 8; 366 | } 367 | if ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_L) // check lowest bit / shift right 368 | { 369 | lz_flag = flags & 1; 370 | flags >>= 1; 371 | flag_bits--; 372 | } 373 | else //if ((lzss->cfg.flags & LZSS_FLAGS_CTRLMASK) == LZSS_FLAGS_CTRL_H) // check highest bit / shift left 374 | { 375 | lz_flag = flags & 0x80; 376 | flags <<= 1; 377 | flag_bits--; 378 | } 379 | 380 | if (lz_flag) { 381 | uint8_t c; 382 | if (inPos >= inSize) 383 | { 384 | if (bytesWritten != NULL) *bytesWritten = outPos; 385 | return LZSS_ERR_EOF_IN; 386 | } 387 | if (outPos >= bufSize) 388 | { 389 | if (bytesWritten != NULL) *bytesWritten = outPos; 390 | return LZSS_ERR_EOF_OUT; 391 | } 392 | c = inData[inPos++]; 393 | buffer[outPos++] = c; 394 | lzss->text_buf[r++] = c; 395 | r &= maskN; 396 | } else { 397 | uint8_t i, j; 398 | unsigned int k, len, ofs; 399 | 400 | if (inPos == inSize) 401 | break; // EOF in this way is valid here 402 | if (inPos+1 >= inSize) 403 | { 404 | if (bytesWritten != NULL) *bytesWritten = outPos; 405 | return LZSS_ERR_EOF_IN; 406 | } 407 | if ((lzss->cfg.flags & LZSS_FLAGS_MTCH_EMASK) == LZSS_FLAGS_MTCH_ELITTLE) 408 | { 409 | i = inData[inPos+0]; 410 | j = inData[inPos+1]; 411 | } 412 | else //if ((lzss->cfg.flags & LZSS_FLAGS_MTCH_EMASK) == LZSS_FLAGS_MTCH_EBIG) 413 | { 414 | j = inData[inPos+0]; 415 | i = inData[inPos+1]; 416 | } 417 | if (lzss->cfg.eosMode == LZSS_EOSM_REF0) 418 | { 419 | if (i == 0 && j == 0) 420 | break; // null-reference ends the stream 421 | } 422 | inPos += 2; 423 | switch(lzss->cfg.flags & LZSS_FLAGS_MTCH_LMASK) 424 | { 425 | case LZSS_FLAGS_MTCH_L_HH: 426 | ofs = ((j & 0x0f) << 8) | i; 427 | len = ((j & 0xf0) >> 4); 428 | break; 429 | case LZSS_FLAGS_MTCH_L_HL: 430 | default: 431 | ofs = ((j & 0xf0) << 4) | i; 432 | len = (j & 0x0f); 433 | break; 434 | case LZSS_FLAGS_MTCH_L_LH: 435 | ofs = (i & 0x0f) | (j << 4); 436 | len = ((i & 0xf0) >> 4); 437 | break; 438 | case LZSS_FLAGS_MTCH_L_LL: 439 | ofs = ((i & 0xf0) >> 4) | (j << 4); 440 | len = (i & 0x0f); 441 | break; 442 | } 443 | len += lzss->THRESHOLD + 1; 444 | if (lzss->cfg.nameTblType == LZSS_NTINIT_NONE) 445 | { 446 | unsigned int ofs_back = (r + lzss->N - ofs) & maskN; 447 | if (ofs_back > outPos) // make sure we don't reference data beyond the start of the file 448 | { 449 | if (bytesWritten != NULL) *bytesWritten = outPos; 450 | return LZSS_ERR_BAD_REF; 451 | } 452 | } 453 | 454 | for (k = 0; k < len; k++) 455 | { 456 | unsigned int offset = (ofs + k) & maskN; 457 | uint8_t c = lzss->text_buf[offset]; 458 | if (outPos >= bufSize) 459 | { 460 | if (bytesWritten != NULL) *bytesWritten = outPos; 461 | return LZSS_ERR_EOF_OUT; 462 | } 463 | buffer[outPos++] = c; 464 | lzss->text_buf[r++] = c; 465 | r &= maskN; 466 | } 467 | } 468 | } 469 | 470 | if (bytesWritten != NULL) *bytesWritten = outPos; 471 | return LZSS_ERR_OK; 472 | } 473 | 474 | void lzssNameTbl_CommonPatterns(LZSS_COMPR* lzss, void* user, size_t nameTblSize, uint8_t* nameTblData) 475 | { 476 | // Important Note: These are non-standard values and ARE used by the compressed data. 477 | unsigned int bufPos; 478 | unsigned int regD0; 479 | unsigned int regD1; 480 | 481 | // LZSS table initialization, originally from Arcus Odyssey X68000, M_DRV.X 482 | // verified using TSTAR.EXE 483 | bufPos = 0x0000; 484 | // 000..CFF (0x0D bytes of 00, 01, 02, ... FF each) 485 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++) 486 | { 487 | for (regD1 = 0x00; regD1 < 0x0D; regD1 ++, bufPos ++) 488 | nameTblData[bufPos] = (uint8_t)regD0; 489 | } 490 | // AD00..ADFF (00 .. FF) 491 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++, bufPos ++) 492 | nameTblData[bufPos] = (uint8_t)regD0; 493 | // AE00..AEFF (FF .. 00) 494 | do 495 | { 496 | regD0 --; 497 | nameTblData[bufPos] = (uint8_t)regD0; 498 | bufPos ++; 499 | } while(regD0 > 0x00); 500 | // AF00..AF7F (0x80 times 00) 501 | for (regD0 = 0x00; regD0 < 0x80; regD0 ++, bufPos ++) 502 | nameTblData[bufPos] = 0x00; 503 | // AF80..AFED (0x6E times 20/space) 504 | //for (regD0 = 0x00; regD0 < 0x80 - lzss->F; regD0 ++, bufPos ++) 505 | for (regD0 = 0x00; regD0 < 0x80; regD0 ++, bufPos ++) // let's just be safe and fill everything 506 | nameTblData[bufPos] = ' '; 507 | 508 | return; 509 | } 510 | -------------------------------------------------------------------------------- /lzss-lib.h: -------------------------------------------------------------------------------- 1 | #ifndef LZSSLIB_H 2 | #define LZSSLIB_H 3 | 4 | #include 5 | 6 | #if !defined(_STDINT_H) && !defined(_STDINT) 7 | #ifdef HAVE_STDINT_H 8 | #include 9 | #else 10 | typedef unsigned char uint8_t; 11 | #endif // !HAVE_STDINT_H 12 | #endif // !_STDINT_H 13 | 14 | 15 | typedef struct _lzss_config LZSS_CFG; 16 | typedef struct _lzss_compressor LZSS_COMPR; 17 | 18 | typedef void (*LZSS_NAMETBL_FUNC)(LZSS_COMPR* lzss, void* user, size_t nameTblSize, uint8_t* nameTblData); 19 | 20 | struct _lzss_config 21 | { 22 | uint8_t flags; // see LZSS_FLAGS_* 23 | uint8_t nameTblType; // see LZSS_NTINIT_* 24 | uint8_t nameTblValue; // (for nameTblType == LZSS_NTINIT_VALUE) 25 | LZSS_NAMETBL_FUNC nameTblFunc; // (for nameTblType == LZSS_NTINIT_FUNC) 26 | void* ntFuncParam; // user parameter for nameTblFunc 27 | int nameTblStartOfs; // offset where the name table buffer starts getting written to 28 | uint8_t eosMode; // see LZSS_EOSM_* 29 | }; 30 | 31 | // control word flags 32 | #define LZSS_FLAGS_CTRL_L 0x00 // LSB first (mask 0x01) 33 | #define LZSS_FLAGS_CTRL_M 0x01 // MSB first (mask 0x80) 34 | #define LZSS_FLAGS_CTRLMASK 0x01 // MSB first (mask 0x80) 35 | // match word flags 36 | // LE: bytes CD AB -> ABCD 37 | // L_HH A, BCD -> len = (j & 0xF0)>>4, ofs = ((j & 0x0F)<<8 | i) 38 | // L_HL B, ACD -> len = (j & 0x0F) , ofs = ((j & 0xF0)<<4 | i) -> default 39 | // L_LH C, ABD -> len = (i & 0xF0)>>4, ofs = ((i & 0x0F) | j<<4) 40 | // L_LL D, ABC -> len = (i & 0x0F) , ofs = ((i & 0xF0)>>4 | j<<4) 41 | // BE: bytes AB CD -> ABCD 42 | // L_HH A, BCD -> len = (i & 0xF0)>>4, ofs = ((i & 0x0F)<<8 | j) 43 | // L_HL B, ACD -> len = (i & 0x0F) , ofs = ((i & 0xF0)<<4 | j) 44 | // L_LH C, ABD -> len = (j & 0xF0)>>4, ofs = ((j & 0x0F) | i<<4) 45 | // L_LL D, ABC -> len = (j & 0x0F) , ofs = ((j & 0xF0)>>4 | i<<4) 46 | #define LZSS_FLAGS_MTCH_ELITTLE 0x00 // Little Endian 47 | #define LZSS_FLAGS_MTCH_EBIG 0x40 // Big Endian 48 | #define LZSS_FLAGS_MTCH_EMASK 0x40 // Endianess mask 49 | 50 | #define LZSS_FLAGS_MTCH_L_HH 0x00 // length nibble: highest 51 | #define LZSS_FLAGS_MTCH_L_HL 0x10 // length nibble: high byte, low nibble 52 | #define LZSS_FLAGS_MTCH_L_LH 0x20 // length nibble: low byte, low nibble 53 | #define LZSS_FLAGS_MTCH_L_LL 0x30 // length nibble: lowest 54 | #define LZSS_FLAGS_MTCH_LMASK 0x30 // length nibble mask 55 | 56 | #define LZSS_FLAGS_MTCH_DEFAULT (LZSS_FLAGS_MTCH_ELITTLE | LZSS_FLAGS_MTCH_L_HL) 57 | 58 | // name table flags 59 | #define LZSS_NTINIT_VALUE 0x00 // initialize name table via constant value 60 | #define LZSS_NTINIT_FUNC 0x01 // initialize name table via function 61 | #define LZSS_NTINIT_NONE 0x02 // don't initialize name table and don't use initial values 62 | 63 | // name table start offset, special values 64 | #define LZSS_NTSTOFS_NF -1 // start at (N-F), i.e. 0xFEE 65 | 66 | // end-of-stream mode 67 | #define LZSS_EOSM_NONE 0x00 68 | #define LZSS_EOSM_REF0 0x01 // end with a "null-reference" (offset 0, length 0) 69 | 70 | 71 | LZSS_COMPR* lzssCreate(const LZSS_CFG* config); 72 | void lzssDestroy(LZSS_COMPR* lzss); 73 | void lzssGetDefaultConfig(LZSS_CFG* config); 74 | const LZSS_CFG* lzssGetConfiguration(const LZSS_COMPR* lzss); 75 | uint8_t lzssEncode(LZSS_COMPR* lzss, size_t bufSize, uint8_t* buffer, size_t* bytesWritten, size_t inSize, const uint8_t* inData); 76 | uint8_t lzssDecode(LZSS_COMPR* lzss, size_t bufSize, uint8_t* buffer, size_t* bytesWritten, size_t inSize, const uint8_t* inData); 77 | void lzssNameTbl_CommonPatterns(LZSS_COMPR* lzss, void* user, size_t nameTblSize, uint8_t* nameTblData); 78 | 79 | 80 | // error codes 81 | #define LZSS_ERR_OK 0x00 // no error 82 | #define LZSS_ERR_EOF_IN 0x01 // reached early end-of-file while reading input buffer 83 | #define LZSS_ERR_EOF_OUT 0x02 // eached end of output buffer before finishing writing 84 | #define LZSS_ERR_BAD_REF 0x03 // invalid backwards reference beyond start of while uninitialized name table is used 85 | 86 | 87 | #endif // LZSSLIB_H 88 | -------------------------------------------------------------------------------- /lzss-tool.c: -------------------------------------------------------------------------------- 1 | /* LZSS compression and decompression tool 2 | Written by Valley Bell, 2024-02 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "lzss-lib.h" 9 | 10 | #ifdef _MSC_VER 11 | #define strdup _strdup 12 | #endif 13 | 14 | 15 | typedef struct archive_header_value 16 | { 17 | char type; 18 | union 19 | { 20 | struct 21 | { 22 | size_t len; 23 | const uint8_t* ptr; 24 | } str; 25 | uint8_t dataVal; 26 | struct 27 | { 28 | uint8_t bytes; // value size (number of bytes) 29 | uint8_t endianess; // see ENDIAN_* constants 30 | uint8_t set_val; // set to 1 for the item that sets values 31 | } size; 32 | } d; 33 | } ARC_HDR_VAL; 34 | #define ENDIAN_LITTLE 0 35 | #define ENDIAN_BIG 1 36 | typedef struct archive_header_specification 37 | { 38 | size_t len; 39 | size_t count; 40 | ARC_HDR_VAL vals[0x10]; 41 | } ARC_HDR_SPEC; 42 | 43 | typedef struct file_data 44 | { 45 | size_t len; 46 | uint8_t* data; 47 | } FILE_DATA; 48 | 49 | #define MODE_NONE 0x00 50 | #define MODE_ENCODE 0x01 51 | #define MODE_DECODE 0x02 52 | 53 | 54 | static void PrintHelp(const char* appName); 55 | static void ParseHeaderSpec(const char* spec, ARC_HDR_SPEC* header); 56 | static size_t WriteArchiveHeader(FILE_DATA* hdrData, const ARC_HDR_SPEC* hdrSpec, size_t decSize, size_t cmpSize); 57 | static uint8_t ReadArchiveHeader(const FILE_DATA* hdrData, const ARC_HDR_SPEC* hdrSpec, size_t* decSize, size_t* cmpSize); 58 | 59 | 60 | int main(int argc, char *argv[]) 61 | { 62 | FILE_DATA inFile; 63 | FILE_DATA outFile; 64 | ARC_HDR_SPEC arcHdrSpec; 65 | uint8_t ret; 66 | FILE* fp; 67 | LZSS_CFG cfg; 68 | LZSS_COMPR* lzss; 69 | int argbase; 70 | uint8_t mode; 71 | 72 | lzssGetDefaultConfig(&cfg); 73 | argbase = 1; 74 | mode = MODE_NONE; 75 | arcHdrSpec.len = 0x00; 76 | arcHdrSpec.count = 0; 77 | while(argbase < argc) 78 | { 79 | char* endptr; 80 | long val; 81 | if (!strcmp(argv[argbase], "-h")) // help 82 | { 83 | PrintHelp(argv[0]); 84 | return 0; 85 | } 86 | else if (!strcmp(argv[argbase], "-e")) // encode 87 | { 88 | mode = MODE_ENCODE; 89 | } 90 | else if (!strcmp(argv[argbase], "-d")) // decode 91 | { 92 | mode = MODE_DECODE; 93 | } 94 | else if (!strcmp(argv[argbase], "-a")) // archive header 95 | { 96 | argbase ++; 97 | if (argbase >= argc) 98 | { 99 | fprintf(stderr, "Insufficient arguments.\n"); 100 | return 1; 101 | } 102 | ParseHeaderSpec(argv[argbase], &arcHdrSpec); 103 | } 104 | else if (!strcmp(argv[argbase], "-n")) // name table initialization value 105 | { 106 | argbase ++; 107 | if (argbase >= argc) 108 | { 109 | fprintf(stderr, "Insufficient arguments.\n"); 110 | return 1; 111 | } 112 | val = strtol(argv[argbase], &endptr, 0); 113 | if (argv[argbase][0] == 'n') 114 | { 115 | cfg.nameTblType = LZSS_NTINIT_NONE; 116 | } 117 | else if (argv[argbase][0] == 'p') 118 | { 119 | cfg.nameTblType = LZSS_NTINIT_FUNC; 120 | cfg.nameTblFunc = lzssNameTbl_CommonPatterns; 121 | cfg.ntFuncParam = NULL; 122 | } 123 | else if (endptr != argv[argbase]) 124 | { 125 | cfg.nameTblType = LZSS_NTINIT_VALUE; 126 | cfg.nameTblValue = (uint8_t)val; 127 | } 128 | else 129 | { 130 | fprintf(stderr, "Unknown name table initialization parameter: %s\n", argv[argbase]); 131 | return 1; 132 | } 133 | } 134 | else if (!strcmp(argv[argbase], "-C")) // control word bit order 135 | { 136 | argbase ++; 137 | if (argbase >= argc) 138 | { 139 | fprintf(stderr, "Insufficient arguments.\n"); 140 | return 1; 141 | } 142 | val = strtol(argv[argbase], &endptr, 0); 143 | if (endptr != argv[argbase]) 144 | { 145 | cfg.flags &= ~LZSS_FLAGS_CTRLMASK; 146 | cfg.flags |= val ? LZSS_FLAGS_CTRL_M : LZSS_FLAGS_CTRL_L; 147 | } 148 | } 149 | else if (!strcmp(argv[argbase], "-R")) // nametable reference value 150 | { 151 | argbase ++; 152 | if (argbase >= argc) 153 | { 154 | fprintf(stderr, "Insufficient arguments.\n"); 155 | return 1; 156 | } 157 | val = strtol(argv[argbase], &endptr, 0); 158 | if (endptr != argv[argbase]) 159 | { 160 | cfg.flags &= ~(LZSS_FLAGS_MTCH_EMASK | LZSS_FLAGS_MTCH_LMASK); 161 | cfg.flags |= (uint8_t)(val << 4); 162 | } 163 | } 164 | else if (!strcmp(argv[argbase], "-O")) // start offset for writing to name table buffer 165 | { 166 | argbase ++; 167 | if (argbase >= argc) 168 | { 169 | fprintf(stderr, "Insufficient arguments.\n"); 170 | return 1; 171 | } 172 | val = strtol(argv[argbase], &endptr, 0); 173 | if (endptr != argv[argbase]) 174 | { 175 | cfg.nameTblStartOfs = (int)val; 176 | } 177 | } 178 | else if (!strcmp(argv[argbase], "-E")) // end-of-stream mode 179 | { 180 | argbase ++; 181 | if (argbase >= argc) 182 | { 183 | fprintf(stderr, "Insufficient arguments.\n"); 184 | return 1; 185 | } 186 | val = strtol(argv[argbase], &endptr, 0); 187 | if (endptr != argv[argbase]) 188 | { 189 | cfg.eosMode = (uint8_t)val; 190 | } 191 | } 192 | else 193 | { 194 | break; 195 | } 196 | argbase ++; 197 | } 198 | 199 | if (argc < argbase + 2) 200 | { 201 | PrintHelp(argv[0]); 202 | return 1; 203 | } 204 | if (mode == MODE_NONE) 205 | { 206 | fprintf(stderr, "No mode specified!\n"); 207 | return 1; 208 | } 209 | 210 | fp = fopen(argv[argbase + 0], "rb"); 211 | if (fp == NULL) 212 | { 213 | fprintf(stderr, "Error opening input file: %s\n", argv[argbase + 0]); 214 | return 2; 215 | } 216 | fseek(fp, 0, SEEK_END); 217 | inFile.len = ftell(fp); 218 | rewind(fp); 219 | inFile.data = (uint8_t*)malloc(inFile.len); 220 | fread(inFile.data, 1, inFile.len, fp); 221 | fclose(fp); 222 | 223 | lzss = lzssCreate(&cfg); 224 | if (mode == MODE_ENCODE) 225 | { 226 | size_t encDataSize; 227 | size_t dataOfs = arcHdrSpec.len; 228 | outFile.len = dataOfs + inFile.len + inFile.len / 8 + 1; 229 | outFile.data = (uint8_t*)malloc(outFile.len); 230 | 231 | ret = lzssEncode(lzss, outFile.len - dataOfs, &outFile.data[dataOfs], &encDataSize, inFile.len, inFile.data); 232 | fprintf(stderr, "In : %u bytes\n", inFile.len); 233 | fprintf(stderr, "Out: %u bytes\n", encDataSize); 234 | fprintf(stderr, "Ratio: %.2f %%\n", (double)encDataSize / inFile.len * 100.0); 235 | 236 | WriteArchiveHeader(&outFile, &arcHdrSpec, inFile.len, encDataSize); 237 | outFile.len = dataOfs + encDataSize; 238 | } 239 | else //if (mode == MODE_DECODE) 240 | { 241 | size_t cmpSize = inFile.len; 242 | size_t dataOfs = arcHdrSpec.len; 243 | outFile.len = inFile.len * 8; // assume compression up to 12.5% 244 | ret = ReadArchiveHeader(&inFile, &arcHdrSpec, &outFile.len, &cmpSize); 245 | if (ret) 246 | { 247 | fprintf(stderr, "Header parsing error!\n"); 248 | return 4; 249 | } 250 | if (inFile.len > dataOfs + cmpSize) 251 | inFile.len = dataOfs + cmpSize; 252 | outFile.data = (uint8_t*)malloc(outFile.len); 253 | ret = lzssDecode(lzss, outFile.len, outFile.data, &outFile.len, inFile.len - dataOfs, &inFile.data[dataOfs]); 254 | } 255 | if (ret != LZSS_ERR_OK) 256 | fprintf(stderr, "LZSS error code %u after writing %u bytes.\n", ret, (unsigned)outFile.len); 257 | 258 | fp = fopen(argv[argbase + 1], "wb"); 259 | if (fp == NULL) 260 | { 261 | fprintf(stderr, "Error opening output file: %s\n", argv[argbase + 1]); 262 | return 3; 263 | } 264 | fwrite(outFile.data, 1, outFile.len, fp); 265 | fclose(fp); 266 | 267 | return 0; 268 | } 269 | 270 | static void PrintHelp(const char* appName) 271 | { 272 | fprintf(stderr, "Usage: %s [mode/options] input.bin output.bin\n", appName); 273 | fprintf(stderr, "\n"); 274 | fprintf(stderr, "Mode: (required)\n"); 275 | fprintf(stderr, " -h show this help screen\n"); 276 | fprintf(stderr, " -e encode / compress\n"); 277 | fprintf(stderr, " -d decode / decompress\n"); 278 | fprintf(stderr, "\n"); 279 | fprintf(stderr, "File format options:\n"); 280 | fprintf(stderr, " -a .. add/read archive header (list of comma-separated values)\n"); 281 | fprintf(stderr, " n - none, don't add any header [default]\n"); 282 | fprintf(stderr, " oNE - original size\n"); 283 | fprintf(stderr, " cNE - compressed size\n"); 284 | fprintf(stderr, " N = size of the value in bytes (2/4)\n"); 285 | fprintf(stderr, " E = endianess (L = little [default], B = big) [optional]\n"); 286 | fprintf(stderr, " sABC - string \"ABC\"\n"); 287 | fprintf(stderr, " bXX - byte XX (hexadecimal value)\n"); 288 | fprintf(stderr, " iXX - ignored (XX is optional and used for writing)\n"); 289 | fprintf(stderr, " Example: -a sLZS,b1A,c2B,o4\n"); 290 | fprintf(stderr, "\n"); 291 | fprintf(stderr, "Compression options:\n"); 292 | fprintf(stderr, " -n n name table initialization value (0x00..0xFF, default: 0x20/space)\n"); 293 | fprintf(stderr, " special values:\n"); 294 | fprintf(stderr, " n - none (prevent lookup to data before beginning of the file)\n"); 295 | fprintf(stderr, " p - various patterns (commonly used by Japanese developers)\n"); 296 | fprintf(stderr, " -C n control word bit order (0 = low->high [default], 1 = high->low)\n"); 297 | fprintf(stderr, " -R n reference word format (bit mask, default: 0x01)\n"); 298 | fprintf(stderr, " mask 0x03: nibble position (0 = highest .. 3 = lowest)\n"); 299 | fprintf(stderr, " mask 0x04: byte endianess (0 = Little Endian, 4 = Big Endian)\n"); 300 | fprintf(stderr, " -O n offset where name table buffer starts getting written to\n"); 301 | fprintf(stderr, " (range: 0x000..0xFFF, default: 0xFEE)\n"); 302 | fprintf(stderr, " -E n end-of-stream mode (0 = no EOS marker, 1 = end with null-reference)\n"); 303 | return; 304 | } 305 | 306 | static void ChooseHeaderSetVal(ARC_HDR_SPEC* header, char item_type) 307 | { 308 | size_t lastSetItem = (size_t)-1; 309 | size_t lastSize = 0; 310 | size_t hdrItem; 311 | 312 | for (hdrItem = 0; hdrItem < header->count; hdrItem++) 313 | { 314 | ARC_HDR_VAL* ahv = &header->vals[hdrItem]; 315 | if (ahv->type != item_type) 316 | continue; 317 | if (ahv->type != 'o' && ahv->type != 'c') 318 | continue; 319 | ahv->d.size.set_val = 0; 320 | if (ahv->d.size.bytes >= lastSize) 321 | { 322 | lastSetItem = hdrItem; 323 | lastSize = ahv->d.size.bytes; 324 | } 325 | } 326 | if (lastSetItem != (size_t)-1) 327 | header->vals[lastSetItem].d.size.set_val = 1; 328 | return; 329 | } 330 | 331 | static void ParseHeaderSpec(const char* spec, ARC_HDR_SPEC* header) 332 | { 333 | char* spec_str; 334 | char* spec_ptr; 335 | char* endptr; 336 | 337 | header->len = 0x00; 338 | header->count = 0; 339 | if (*spec == '\0') 340 | return; 341 | 342 | spec_str = strdup(spec); 343 | spec_ptr = spec_str; 344 | while (spec_ptr != NULL && *spec_ptr != '\0' && header->count < 0x10) 345 | { 346 | ARC_HDR_VAL* ahv = &header->vals[header->count]; 347 | char mode = *spec_ptr; 348 | char* sep_ptr = strchr(spec_ptr, ','); 349 | if (sep_ptr != NULL) 350 | *sep_ptr = '\0'; 351 | spec_ptr += 1; 352 | 353 | if (mode == 'n') 354 | { 355 | header->count = 0; 356 | break; 357 | } 358 | else if (mode == 'o' || mode == 'c') 359 | { 360 | size_t param_len = strlen(spec_ptr); 361 | uint8_t is_good = 1; 362 | ahv->type = mode; 363 | ahv->d.size.bytes = 4; 364 | ahv->d.size.endianess = ENDIAN_LITTLE; 365 | ahv->d.size.set_val = 0; 366 | 367 | // parse value size digit 368 | if (param_len >= 1) 369 | { 370 | if (spec_ptr[0] >= '0' && spec_ptr[0] <= '9') 371 | { 372 | ahv->d.size.bytes = spec_ptr[0] - '0'; 373 | if (!(ahv->d.size.bytes == 2 || ahv->d.size.bytes == 4)) 374 | { 375 | fprintf(stderr, "Archive header mode %c: Value size is %u, but can only be 2 or 4.\n", 376 | mode, ahv->d.size.bytes); 377 | is_good = 0; 378 | } 379 | } 380 | else 381 | { 382 | fprintf(stderr, "Archive header mode %c: Invalid value size: %c\n", mode, spec_ptr[0]); 383 | is_good = 0; 384 | } 385 | } 386 | // parse endianess 387 | if (param_len >= 2) 388 | { 389 | if (spec_ptr[1] == 'L' || spec_ptr[1] == 'l') 390 | ahv->d.size.endianess = ENDIAN_LITTLE; 391 | else if (spec_ptr[1] == 'B' || spec_ptr[1] == 'b') 392 | ahv->d.size.endianess = ENDIAN_BIG; 393 | else 394 | { 395 | fprintf(stderr, "Archive header mode %c: Invalid endianess: %c\n", mode, spec_ptr[1]); 396 | is_good = 0; 397 | } 398 | } 399 | if (is_good) 400 | { 401 | header->count ++; 402 | header->len += ahv->d.size.bytes; 403 | } 404 | } 405 | else if (mode == 's') 406 | { 407 | ahv->type = mode; 408 | ahv->d.str.ptr = spec + (spec_ptr - spec_str); 409 | ahv->d.str.len = strlen(spec_ptr); 410 | header->count ++; 411 | header->len += ahv->d.str.len; 412 | } 413 | else if (mode == 'b' || mode == 'i') 414 | { 415 | ahv->type = mode; 416 | ahv->d.dataVal = (uint8_t)strtoul(spec_ptr, &endptr, 0x10); 417 | if (mode == 'i' && *spec_ptr == '\0') 418 | { 419 | ahv->d.dataVal = 0x00; 420 | header->count ++; 421 | header->len += 0x01; 422 | } 423 | else if (*endptr == '\0' && *spec_ptr != '\0') 424 | { 425 | header->count ++; 426 | header->len += 0x01; 427 | } 428 | else 429 | { 430 | fprintf(stderr, "Archive header specification: Invalid byte value: %s\n", spec_ptr); 431 | } 432 | } 433 | else 434 | { 435 | fprintf(stderr, "Archive header specification: Invalid header value type: %c\n", mode); 436 | } 437 | if (sep_ptr == NULL) 438 | break; 439 | spec_ptr = sep_ptr + 1; 440 | } 441 | free(spec_str); 442 | ChooseHeaderSetVal(header, 'o'); 443 | ChooseHeaderSetVal(header, 'c'); 444 | return; 445 | } 446 | 447 | 448 | static size_t ReadLE(const uint8_t* buffer, uint8_t valSize) 449 | { 450 | size_t pos; 451 | size_t val = 0x00; 452 | for (pos = valSize; pos > 0; pos--) 453 | { 454 | val <<= 8; 455 | val |= buffer[pos - 1]; 456 | } 457 | return val; 458 | } 459 | 460 | static void WriteLE(uint8_t* buffer, uint8_t valSize, size_t val) 461 | { 462 | size_t pos; 463 | for (pos = 0; pos < valSize; pos++) 464 | { 465 | buffer[pos] = val & 0xFF; 466 | val >>= 8; 467 | } 468 | return; 469 | } 470 | 471 | static size_t ReadBE(const uint8_t* buffer, uint8_t valSize) 472 | { 473 | size_t pos; 474 | size_t val = 0x00; 475 | for (pos = 0; pos < valSize; pos++) 476 | { 477 | val <<= 8; 478 | val |= buffer[pos]; 479 | } 480 | return val; 481 | } 482 | 483 | static void WriteBE(uint8_t* buffer, uint8_t valSize, size_t val) 484 | { 485 | size_t pos; 486 | for (pos = valSize; pos > 0; pos--) 487 | { 488 | buffer[pos - 1] = val & 0xFF; 489 | val >>= 8; 490 | } 491 | return; 492 | } 493 | 494 | static size_t WriteArchiveHeader(FILE_DATA* hdrData, const ARC_HDR_SPEC* hdrSpec, size_t decSize, size_t cmpSize) 495 | { 496 | size_t hdrItem; 497 | size_t pos; 498 | if (hdrSpec->len == 0) 499 | return 0x00; 500 | 501 | pos = 0x00; 502 | for (hdrItem = 0; hdrItem < hdrSpec->count; hdrItem++) 503 | { 504 | const ARC_HDR_VAL* ahv = &hdrSpec->vals[hdrItem]; 505 | if (pos >= hdrData->len) 506 | break; // should never happen 507 | switch (ahv->type) 508 | { 509 | case 'n': 510 | hdrData->len = 0x00; 511 | return 0x00; 512 | case 'o': 513 | case 'c': 514 | { 515 | size_t val = 0; 516 | if (pos + ahv->d.size.bytes > hdrData->len) 517 | break; 518 | if (ahv->type == 'o') 519 | val = decSize; 520 | else //if (ahv->type == 'c') 521 | val = cmpSize; 522 | if (ahv->d.size.endianess == ENDIAN_LITTLE) 523 | WriteLE(&hdrData->data[pos], ahv->d.size.bytes, val); 524 | else //if (ahv->d.size.endianess == ENDIAN_BIG) 525 | WriteBE(&hdrData->data[pos], ahv->d.size.bytes, val); 526 | pos += ahv->d.size.bytes; 527 | break; 528 | } 529 | case 's': 530 | { 531 | size_t copyLen = ahv->d.str.len; 532 | if (pos + copyLen > hdrData->len) 533 | copyLen = hdrSpec->len - pos; 534 | memcpy(&hdrData->data[pos], ahv->d.str.ptr, copyLen); 535 | pos += ahv->d.str.len; 536 | break; 537 | } 538 | case 'b': 539 | case 'i': 540 | hdrData->data[pos] = ahv->d.dataVal; 541 | pos += 0x01; 542 | break; 543 | } 544 | } 545 | return pos; 546 | } 547 | 548 | static uint8_t ReadArchiveHeader(const FILE_DATA* hdrData, const ARC_HDR_SPEC* hdrSpec, size_t* decSize, size_t* cmpSize) 549 | { 550 | size_t hdrItem; 551 | size_t pos; 552 | if (hdrSpec->len == 0) 553 | return 0; 554 | 555 | pos = 0x00; 556 | for (hdrItem = 0; hdrItem < hdrSpec->count; hdrItem++) 557 | { 558 | const ARC_HDR_VAL* ahv = &hdrSpec->vals[hdrItem]; 559 | if (pos >= hdrData->len) 560 | break; // should never happen 561 | switch (ahv->type) 562 | { 563 | case 'n': 564 | return 0; 565 | case 'o': 566 | case 'c': 567 | { 568 | size_t val; 569 | if (pos + ahv->d.size.bytes > hdrData->len) 570 | break; 571 | if (ahv->d.size.endianess == ENDIAN_LITTLE) 572 | val = ReadLE(&hdrData->data[pos], ahv->d.size.bytes); 573 | else //if (ahv->d.size.endianess == ENDIAN_BIG) 574 | val = ReadBE(&hdrData->data[pos], ahv->d.size.bytes); 575 | if (ahv->d.size.set_val) 576 | { 577 | if (ahv->type == 'o') 578 | *decSize = val; 579 | else //if (ahv->type == 'c') 580 | *cmpSize = val; 581 | } 582 | pos += ahv->d.size.bytes; 583 | break; 584 | } 585 | case 's': 586 | { 587 | size_t checkLen = ahv->d.str.len; 588 | if (pos + checkLen > hdrData->len) 589 | checkLen = hdrSpec->len - pos; 590 | if (memcmp(&hdrData->data[pos], ahv->d.str.ptr, checkLen)) 591 | { 592 | fprintf(stderr, "Header mismatch at offset 0x%02X: \"%.*s\" != \"%.*s\"\n", 593 | pos, checkLen, (const char*)&hdrData->data[pos], ahv->d.str.len, ahv->d.str.ptr); 594 | return 1; 595 | } 596 | pos += ahv->d.str.len; 597 | break; 598 | } 599 | case 'b': 600 | if (hdrData->data[pos] != ahv->d.dataVal) 601 | { 602 | fprintf(stderr, "Header mismatch at offset 0x%02X: byte 0x%02X != 0x%02X\n", 603 | pos, hdrData->data[pos], ahv->d.dataVal); 604 | return 1; 605 | } 606 | pos += 0x01; 607 | break; 608 | case 'i': 609 | pos += 0x01; 610 | break; 611 | } 612 | } 613 | return 0; 614 | } 615 | -------------------------------------------------------------------------------- /mrndec.c: -------------------------------------------------------------------------------- 1 | // Mirinae Software Decompressor 2 | // ----------------------------- 3 | // Valley Bell, written on 2021-12-07 4 | #include 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | #include 9 | #endif 10 | 11 | #ifdef HAVE_STDINT 12 | 13 | #include 14 | typedef uint8_t UINT8; 15 | typedef uint16_t UINT16; 16 | typedef int16_t INT16; 17 | 18 | #else // ! HAVE_STDINT 19 | 20 | typedef unsigned char UINT8; 21 | typedef unsigned short UINT16; 22 | typedef signed short INT16; 23 | 24 | #endif // HAVE_STDINT 25 | 26 | 27 | static void DecompressFile(size_t inSize, const UINT8* inData, const char* fileName); 28 | static UINT16 ReadLE16(const UINT8* data); 29 | 30 | 31 | static UINT8 decodeKey = 0x6B; 32 | static size_t songCnt = 20; 33 | 34 | int main(int argc, char* argv[]) 35 | { 36 | int argbase; 37 | FILE* hFile; 38 | size_t inLen; 39 | UINT8* inData; 40 | #ifdef _WIN32 41 | FILETIME ftWrite; 42 | HANDLE hWinFile; 43 | #endif 44 | 45 | printf("Mirinae Software Decompressor\n-----------------------------\n"); 46 | if (argc < 2) 47 | { 48 | printf("Usage: %s compressed.bin decompressed.bin\n"); 49 | return 0; 50 | } 51 | argbase = 1; 52 | /*while(argbase < argc && argv[argbase][0] == '-') 53 | { 54 | if (argv[argbase][1] == '\0') 55 | { 56 | } 57 | else 58 | break; 59 | argbase ++; 60 | }*/ 61 | if (argc < argbase + 2) 62 | { 63 | printf("Insufficient parameters!\n"); 64 | return 0; 65 | } 66 | 67 | hFile = fopen(argv[argbase + 0], "rb"); 68 | if (hFile == NULL) 69 | return 1; 70 | 71 | fseek(hFile, 0, SEEK_END); 72 | inLen = ftell(hFile); 73 | if (inLen > 0x1000000) 74 | inLen = 0x1000000; // limit to 16 MB 75 | 76 | inData = (UINT8*)malloc(inLen); 77 | fseek(hFile, 0, SEEK_SET); 78 | fread(inData, 0x01, inLen, hFile); 79 | 80 | fclose(hFile); 81 | 82 | #ifdef _WIN32 83 | ftWrite.dwLowDateTime = ftWrite.dwHighDateTime = 0; 84 | hWinFile = CreateFile(argv[argbase + 0], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 85 | if (hWinFile != INVALID_HANDLE_VALUE) 86 | { 87 | GetFileTime(hWinFile, NULL, NULL, &ftWrite); 88 | CloseHandle(hWinFile); 89 | } 90 | #endif 91 | 92 | DecompressFile(inLen, inData, argv[argbase + 1]); 93 | 94 | #ifdef _WIN32 95 | if (ftWrite.dwLowDateTime != 0 && ftWrite.dwHighDateTime != 0) 96 | { 97 | hWinFile = CreateFile(argv[argbase + 1], GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 98 | if (hWinFile != INVALID_HANDLE_VALUE) 99 | { 100 | SetFileTime(hWinFile, NULL, NULL, &ftWrite); 101 | CloseHandle(hWinFile); 102 | } 103 | } 104 | #endif 105 | 106 | free(inData); 107 | 108 | return 0; 109 | } 110 | 111 | static void DecompressFile(size_t inSize, const UINT8* inData, const char* fileName) 112 | { 113 | FILE* hFile; 114 | UINT8* decBuf; 115 | size_t inPos; 116 | size_t outPos; 117 | UINT16 ctrlData; // reg BP 118 | UINT8 ctrlBits; // reg DL 119 | UINT8 carry; 120 | UINT16 copyCnt; // reg CX 121 | INT16 copyOfs; // reg BX 122 | 123 | decBuf = (UINT8*)malloc(0x10000); // 64 KB for now 124 | 125 | inPos = 0x00; 126 | outPos = 0x00; 127 | 128 | ctrlData = ReadLE16(&inData[inPos]); inPos += 0x02; 129 | ctrlBits = 16; 130 | copyCnt = 0; 131 | while(inPos < inSize && outPos < 0x10000) 132 | { 133 | //loc_10C44 134 | carry = (ctrlData & 0x01); 135 | ctrlData >>= 1; 136 | ctrlBits --; 137 | if (ctrlBits == 0) 138 | { 139 | ctrlData = ReadLE16(&inData[inPos]); inPos += 0x02; 140 | ctrlBits = 16; 141 | } 142 | if (carry) 143 | { 144 | decBuf[outPos] = inData[inPos]; 145 | inPos ++; outPos ++; 146 | continue; 147 | } 148 | 149 | //loc_10C54 150 | carry = (ctrlData & 0x01); 151 | ctrlData >>= 1; 152 | ctrlBits --; 153 | if (ctrlBits == 0) 154 | { 155 | ctrlData = ReadLE16(&inData[inPos]); inPos += 0x02; 156 | ctrlBits = 16; 157 | } 158 | //loc_10C5F 159 | if (! carry) 160 | { 161 | carry = (ctrlData & 0x01); 162 | ctrlData >>= 1; 163 | ctrlBits --; 164 | if (ctrlBits == 0) 165 | { 166 | ctrlData = ReadLE16(&inData[inPos]); inPos += 0x02; 167 | ctrlBits = 16; 168 | } 169 | //loc_10C6C 170 | copyCnt = carry; 171 | 172 | carry = (ctrlData & 0x01); 173 | ctrlData >>= 1; 174 | ctrlBits --; 175 | if (ctrlBits == 0) 176 | { 177 | ctrlData = ReadLE16(&inData[inPos]); inPos += 0x02; 178 | ctrlBits = 16; 179 | } 180 | //loc_10C79 181 | copyCnt = (copyCnt << 1) | carry; 182 | 183 | copyCnt += 2; 184 | copyOfs = -0x100 + inData[inPos]; inPos += 0x01; 185 | } 186 | else 187 | { 188 | //loc_10C84 189 | UINT16 ax = ReadLE16(&inData[inPos]); inPos += 0x02; 190 | copyOfs = -0x2000 + (ax & 0x1FFF); 191 | copyCnt = (ax >> 13); 192 | if (copyCnt != 0) 193 | { 194 | //loc_10C9E 195 | copyCnt = copyCnt + 2; 196 | } 197 | else 198 | { 199 | UINT8 cmd; // reg AL 200 | cmd = inData[inPos]; inPos += 0x01; 201 | if (cmd == 0) 202 | { 203 | //loc_10CAC 204 | copyCnt = 0; // segment reset - nothing to do here 205 | continue; 206 | } 207 | else if (cmd == 1) 208 | { 209 | break; // file end 210 | } 211 | else 212 | { 213 | copyCnt = cmd + 1; 214 | } 215 | } 216 | } 217 | for (; copyCnt > 0; copyCnt --) 218 | { 219 | decBuf[outPos] = decBuf[outPos + copyOfs]; 220 | outPos ++; 221 | } 222 | } 223 | 224 | printf("%u bytes -> %u bytes.\n", inPos, outPos); 225 | 226 | hFile = fopen(fileName, "wb"); 227 | if (hFile == NULL) 228 | { 229 | printf("Error writing %s!\n", fileName); 230 | } 231 | else 232 | { 233 | fwrite(decBuf, 1, outPos, hFile); 234 | fclose(hFile); 235 | } 236 | free(decBuf); 237 | 238 | return; 239 | } 240 | 241 | static UINT16 ReadLE16(const UINT8* data) 242 | { 243 | return (data[0x00] << 0) | (data[0x01] << 8); 244 | } 245 | -------------------------------------------------------------------------------- /piyo_dec.c: -------------------------------------------------------------------------------- 1 | // PANDA HOUSE 'PIYO' decoder 2 | #include 3 | #include 4 | #include // for memcmp/memcpy 5 | 6 | typedef unsigned char UINT8; 7 | typedef unsigned short UINT16; 8 | 9 | static UINT16 ReadLE16(const UINT8* data); 10 | static void WriteLE16(UINT8* buffer, UINT16 value); 11 | static void DecodeData(UINT8* dst, const UINT8* src, size_t len, UINT8 keyInit); 12 | static size_t DecodeCOMData(size_t srcLen, UINT8* data); 13 | static size_t DecodeEXEData(size_t srcLen, UINT8* data); 14 | 15 | 16 | int main(int argc, char* argv[]) 17 | { 18 | int argbase; 19 | FILE* hFile; 20 | size_t srcLen; 21 | size_t decLen; 22 | UINT8* data; 23 | 24 | printf("PANDA HOUSE 'PIYO' decoder\n--------------------------\n"); 25 | if (argc < 3) 26 | { 27 | printf("Usage: %s MFD.COM MFD_DEC.COM\n", argv[0]); 28 | printf("Usage: %s MAXG.EXE MAXG_DEC.EXE\n", argv[0]); 29 | return 0; 30 | } 31 | 32 | argbase = 1; 33 | hFile = fopen(argv[argbase + 0], "rb"); 34 | if (hFile == NULL) 35 | { 36 | printf("Error opening file!\n"); 37 | return 1; 38 | } 39 | 40 | fseek(hFile, 0x00, SEEK_END); 41 | srcLen = ftell(hFile); 42 | if (srcLen > 0x100000) // 1 MB 43 | srcLen = 0x100000; 44 | 45 | fseek(hFile, 0x00, SEEK_SET); 46 | data = (UINT8*)malloc(srcLen); 47 | fread(data, 0x01, srcLen, hFile); 48 | 49 | fclose(hFile); 50 | 51 | if (data[0x00] == 0xE9) // 8086 jump instruction 52 | { 53 | decLen = DecodeCOMData(srcLen, data); 54 | } 55 | else if (data[0x00] == 'M' && data[0x01] == 'Z') // "MZ" signature 56 | { 57 | decLen = DecodeEXEData(srcLen, data); 58 | } 59 | else 60 | { 61 | printf("Error: Unknown executable type!\n"); 62 | } 63 | 64 | if (decLen != (size_t)-1) 65 | { 66 | hFile = fopen(argv[argbase + 1], "wb"); 67 | if (hFile == NULL) 68 | { 69 | free(data); 70 | printf("Error opening %s!\n", argv[argbase + 1]); 71 | return 2; 72 | } 73 | fwrite(data, 0x01, decLen, hFile); 74 | fclose(hFile); 75 | 76 | printf("Done.\n"); 77 | } 78 | 79 | free(data); 80 | 81 | #ifdef _DEBUG 82 | getchar(); 83 | #endif 84 | 85 | return 0; 86 | } 87 | 88 | static UINT16 ReadLE16(const UINT8* data) 89 | { 90 | return (data[0x01] << 8) | (data[0x00] << 0); 91 | } 92 | 93 | static void WriteLE16(UINT8* buffer, UINT16 value) 94 | { 95 | buffer[0x00] = (value >> 0) & 0xFF; 96 | buffer[0x01] = (value >> 8) & 0xFF; 97 | 98 | return; 99 | } 100 | 101 | static void DecodeData(UINT8* dst, const UINT8* src, size_t len, UINT8 keyInit) 102 | { 103 | size_t pos; 104 | UINT8 val; 105 | UINT8 key = keyInit; 106 | for (pos = 0x00; pos < len; pos ++) 107 | { 108 | val = src[pos]; // LODSB 109 | val = (val << 1) | (val >> 7); // ROL AL, 1 110 | val ^= key; // XOR AL, BL 111 | key += val; // ADD BL, AL 112 | dst[pos] = val; // STOSB 113 | } 114 | return; 115 | } 116 | 117 | static size_t DecodeCOMData(size_t srcLen, UINT8* data) 118 | { 119 | size_t decLen; 120 | 121 | if (0x0A > srcLen || memcmp(&data[0x06], "PIYO", 0x04)) 122 | { 123 | printf("PIYO signature not found!\n"); 124 | return (size_t)-1; 125 | } 126 | 127 | /* layout of encrypted COM file: 128 | 00..02: [code] instruction "JMP decode" 129 | 03..04: size of data to decode 130 | 05 : initial value of decode register 131 | 06..09: "PIYO" signature (ignored) 132 | 0A.. : encoded data 133 | */ 134 | decLen = ReadLE16(&data[0x03]); 135 | DecodeData(&data[0x00], &data[0x0A], decLen, data[0x05]); 136 | 137 | return decLen; 138 | } 139 | 140 | /* MZ EXE file header structure: 141 | Pos Len Description 142 | 00 02 "MZ" signature 143 | 02 02 number of bytes in last 512-byte page 144 | 04 02 number of 512-byte pages (includes partial past page) 145 | 06 02 number of relocation entries 146 | 08 02 header size in 16-byte paragraphs 147 | 0A 02 additional allocation: minimum number of paragraphs 148 | 0C 02 additional allocation: maximum number of paragraphs (often 0FFFFh) 149 | 0E 02 initial value of SS register 150 | 10 02 initial value of SP register 151 | 12 02 checksum (can be 0) 152 | 14 02 initial value of IP register 153 | 16 02 initial value of CS register 154 | 18 02 offset of relocation table 155 | 1A 02 overlay number (usually 0) 156 | */ 157 | static size_t DecodeEXEData(size_t srcLen, UINT8* data) 158 | { 159 | UINT16 hdrSize = ReadLE16(&data[0x08]); // in paragraphs of 0x10 bytes 160 | UINT16 initIP = ReadLE16(&data[0x14]); 161 | UINT16 initCS = ReadLE16(&data[0x16]); 162 | size_t baseOfs = hdrSize * 0x10; 163 | size_t piyoBase = baseOfs + initCS * 0x10 + initIP; 164 | size_t decLen; 165 | 166 | if (piyoBase + 0x19 > srcLen || memcmp(&data[piyoBase + 0x15], "PIYO", 0x04)) 167 | { 168 | printf("PIYO signature not found!\n"); 169 | return (size_t)-1; 170 | } 171 | 172 | /* layout of decode segment in encrypted EXE file: 173 | 00..04: [code] PUSHF / CLI / CALL base+0005h 174 | 05..07: [code] POP BX / JMP $+17h (jump over PIYO decode block) 175 | 08..09: register CS (code segment) -> MZ offset 16h 176 | 0A..0B: register IP (instruction pointer) -> MZ offset 14h 177 | 0C..0D: register SS (stack segment) -> MZ offset 0Eh 178 | 0E..0F: register SP (stack pointer) -> MZ offset 10h 179 | 10..11: size of data to decode 180 | 12..13: high word of decode size? (unused) 181 | 14 : initial value of decode register 182 | 15..18: "PIYO" signature (ignored) 183 | 19..1A: number of bytes in last page -> MZ offset 02h 184 | 1B..1C: number of 512-byte pages -> MZ offset 04h 185 | 1D.. : [code] decoding logic 186 | */ 187 | 188 | // decode program data 189 | decLen = ReadLE16(&data[piyoBase + 0x10]); 190 | DecodeData(&data[baseOfs], &data[baseOfs], decLen, data[piyoBase + 0x14]); 191 | 192 | // copy 8086 register values into MZ header 193 | memcpy(&data[0x16], &data[piyoBase + 0x08], 0x02); // copy register CS 194 | memcpy(&data[0x14], &data[piyoBase + 0x0A], 0x02); // copy register IP 195 | memcpy(&data[0x0E], &data[piyoBase + 0x0C], 0x04); // copy register SS:SP 196 | memcpy(&data[0x02], &data[piyoBase + 0x19], 0x04); // copy MZ page info 197 | 198 | return srcLen - 0x80; 199 | } 200 | -------------------------------------------------------------------------------- /rekiai_dec.c: -------------------------------------------------------------------------------- 1 | // Rekiai Song Unpacker 2 | // -------------------- 3 | // Valley Bell, written on 2020-12-08 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stdtype.h" 9 | 10 | 11 | static const char* GetFileExt(const char* filePath); 12 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 13 | static UINT16 ReadBE16(const UINT8* Data); 14 | 15 | 16 | int main(int argc, char* argv[]) 17 | { 18 | int argbase; 19 | FILE* hFile; 20 | UINT32 inLen; 21 | UINT8* inData; 22 | 23 | printf("Rekiai Song Unpacker\n--------------------\n"); 24 | if (argc < 3) 25 | { 26 | printf("Usage: rekiai_dec.exe [Options] input.mf output_name\n"); 27 | printf("Supported games: Rekiai (PC-98)\n"); 28 | return 0; 29 | } 30 | 31 | argbase = 1; 32 | if (argc < argbase + 2) 33 | { 34 | printf("Insufficient parameters!\n"); 35 | return 0; 36 | } 37 | 38 | hFile = fopen(argv[argbase + 0], "rb"); 39 | if (hFile == NULL) 40 | return 1; 41 | 42 | fseek(hFile, 0, SEEK_END); 43 | inLen = ftell(hFile); 44 | if (inLen > 0x100000) 45 | inLen = 0x100000; // limit to 1 MB 46 | 47 | inData = (UINT8*)malloc(inLen); 48 | fseek(hFile, 0, SEEK_SET); 49 | fread(inData, 0x01, inLen, hFile); 50 | 51 | fclose(hFile); 52 | 53 | DecompressArchive(inLen, inData, argv[argbase + 1]); 54 | 55 | free(inData); 56 | 57 | return 0; 58 | } 59 | 60 | static const char* GetFileExt(const char* filePath) 61 | { 62 | const char* fileExt; 63 | const char* dirSep; 64 | const char* dirSep2; 65 | 66 | dirSep = strrchr(filePath, '/'); 67 | dirSep2 = strrchr(filePath, '\\'); 68 | if (dirSep == NULL || (dirSep2 != NULL && dirSep < dirSep2)) 69 | dirSep = dirSep2; 70 | if (dirSep != NULL) 71 | filePath = dirSep + 1; 72 | 73 | fileExt = strrchr(filePath, '.'); 74 | return (fileExt != NULL) ? fileExt : (filePath + strlen(filePath)); 75 | } 76 | 77 | static void DecompressArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 78 | { 79 | static const char* PREFIXES[6] = {".TXT", ".SSG", ".OPN", "_N.MS", "_B2.MS", "_GS.MS"}; 80 | const char* fileExt; 81 | char* outName; 82 | char* outExt; 83 | UINT32 fileCnt; 84 | UINT32 curFile; 85 | UINT32 arcPos; 86 | UINT32 filePos; 87 | UINT16 fileLen; 88 | UINT16 wrtLen; 89 | FILE* hFile; 90 | 91 | fileExt = GetFileExt(fileName); 92 | outName = (char*)malloc(strlen(fileName) + 0x10); 93 | strcpy(outName, fileName); 94 | outExt = outName + (fileExt - fileName); 95 | 96 | // extract everything 97 | fileCnt = 6; 98 | arcPos = 0x04; 99 | filePos = 0x10; 100 | for (curFile = 0; curFile < fileCnt; curFile ++, arcPos += 0x02) 101 | { 102 | fileLen = ReadBE16(&arcData[arcPos]); 103 | 104 | // generate file name (ABC.MF -> _GS.MS) 105 | strcpy(outExt, PREFIXES[curFile]); 106 | 107 | if (! fileLen) 108 | { 109 | printf("Skipping %s (no data)\n", outName); 110 | continue; 111 | } 112 | 113 | wrtLen = fileLen; 114 | if (curFile == 0 && arcData[filePos + wrtLen - 1] == '\0') 115 | wrtLen --; // for song title, omit the trailing \0 character 116 | 117 | printf("Writing %s ...\n", outName); 118 | hFile = fopen(outName, "wb"); 119 | if (hFile == NULL) 120 | { 121 | printf("Error writing %s!\n", outName); 122 | break; 123 | } 124 | fwrite(&arcData[filePos], 1, wrtLen, hFile); 125 | fclose(hFile); 126 | 127 | filePos += fileLen; 128 | } 129 | printf("Done.\n"); 130 | free(outName); 131 | 132 | return; 133 | } 134 | 135 | static UINT16 ReadBE16(const UINT8* Data) 136 | { 137 | return (Data[0x00] << 8) | (Data[0x01] << 0); 138 | } 139 | -------------------------------------------------------------------------------- /stdtype.h: -------------------------------------------------------------------------------- 1 | #ifndef __CST_STDTYPE_H__ 2 | #define __CST_STDTYPE_H__ 3 | 4 | #ifdef HAVE_STDINT 5 | 6 | #include 7 | 8 | typedef uint8_t UINT8; 9 | typedef int8_t INT8; 10 | typedef uint16_t UINT16; 11 | typedef int16_t INT16; 12 | typedef uint32_t UINT32; 13 | typedef int32_t INT32; 14 | typedef uint64_t UINT64; 15 | typedef int64_t INT64; 16 | 17 | #else // ! HAVE_STDINT 18 | 19 | // typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h) 20 | // 8-bit values 21 | typedef unsigned char UINT8; 22 | typedef signed char INT8; 23 | 24 | // 16-bit values 25 | typedef unsigned short UINT16; 26 | typedef signed short INT16; 27 | 28 | // 32-bit values 29 | #ifndef _WINDOWS_H 30 | typedef unsigned int UINT32; 31 | typedef signed int INT32; 32 | 33 | // 64-bit values 34 | #ifdef _MSC_VER 35 | typedef unsigned __int64 UINT64; 36 | typedef signed __int64 INT64; 37 | #else 38 | __extension__ typedef unsigned long long UINT64; 39 | __extension__ typedef signed long long INT64; 40 | #endif 41 | #endif // _WINDOWS_H 42 | 43 | #endif // HAVE_STDINT 44 | 45 | #endif // __CST_STDTYPE_H__ 46 | -------------------------------------------------------------------------------- /wolfteam_dec.c: -------------------------------------------------------------------------------- 1 | // Wolfteam Decompressor 2 | // --------------------- 3 | // Valley Bell, written on 2019-10-23 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stdtype.h" 9 | 10 | 11 | // Byte Order constants 12 | #define BO_LE 0x01 // Little Endian 13 | #define BO_BE 0x02 // Big Endian 14 | 15 | static void DecompressFile(const UINT8* inData, const char* fileName); 16 | static void DecompressMultiFile(UINT32 arcSize, const UINT8* arcData, const char* fileName); 17 | static void ExtractArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 18 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData); 19 | static UINT16 ReadLE16(const UINT8* data); 20 | static UINT16 ReadBE16(const UINT8* data); 21 | static UINT16 ReadUInt16(const UINT8* data); 22 | static UINT32 ReadLE32(const UINT8* data); 23 | static UINT32 ReadBE32(const UINT8* data); 24 | static UINT32 ReadUInt32(const UINT8* data); 25 | 26 | static UINT8 fmtByteOrder = 0; 27 | 28 | int main(int argc, char* argv[]) 29 | { 30 | int argbase; 31 | FILE* hFile; 32 | UINT32 inLen; 33 | UINT8* inData; 34 | UINT8 fileFmt; 35 | 36 | printf("Wolfteam Decompressor\n---------------------\n"); 37 | if (argc < 3) 38 | { 39 | printf("Usage: %s [Options] input.bin output.bin\n", argv[0]); 40 | printf("Options:\n"); 41 | printf(" -1 multiple concatenated compressed files (default)\n"); 42 | printf(" -2 uncompressed archive with TOC\n"); 43 | printf(" -l Byte Order: Little Endian\n"); 44 | printf(" -b Byte Order: Big Endian\n"); 45 | printf(" Note: File names are generated using the output name.\n"); 46 | printf(" Example: output.bin -> output_00.bin, output_01.bin, etc.\n"); 47 | return 0; 48 | } 49 | 50 | fileFmt = 0; 51 | fmtByteOrder = 0; 52 | argbase = 1; 53 | while(argbase < argc && argv[argbase][0] == '-') 54 | { 55 | if (argv[argbase][1] == 'l') 56 | { 57 | fileFmt = 1; 58 | } 59 | else if (argv[argbase][1] == '2') 60 | { 61 | fileFmt = 2; 62 | } 63 | else if (argv[argbase][1] == 'l') 64 | { 65 | fmtByteOrder = BO_LE; 66 | } 67 | else if (argv[argbase][1] == 'b') 68 | { 69 | fmtByteOrder = BO_BE; 70 | } 71 | else 72 | break; 73 | argbase ++; 74 | } 75 | if (argc < argbase + 2) 76 | { 77 | printf("Insufficient parameters!\n"); 78 | return 0; 79 | } 80 | 81 | hFile = fopen(argv[argbase + 0], "rb"); 82 | if (hFile == NULL) 83 | return 1; 84 | 85 | fseek(hFile, 0, SEEK_END); 86 | inLen = ftell(hFile); 87 | if (inLen > 0x1000000) 88 | inLen = 0x1000000; // limit to 16 MB 89 | 90 | inData = (UINT8*)malloc(inLen); 91 | fseek(hFile, 0, SEEK_SET); 92 | fread(inData, 0x01, inLen, hFile); 93 | 94 | fclose(hFile); 95 | 96 | if (fileFmt == 0) 97 | { 98 | fileFmt = 1; 99 | //printf("Detected format: %u\n", fileFmt); 100 | } 101 | switch(fileFmt) 102 | { 103 | case 1: 104 | if (fmtByteOrder == 0) 105 | { 106 | // read data length of first file 107 | UINT32 valLE = ReadLE32(&inData[0x00]); 108 | UINT32 valBE = ReadBE32(&inData[0x00]); 109 | // the value with the correct order is smaller 110 | fmtByteOrder = (valLE < valBE) ? BO_LE : BO_BE; 111 | printf("Detected byte order: %s Endian\n", (fileFmt == BO_LE) ? "Little" : "Big"); 112 | } 113 | DecompressMultiFile(inLen, inData, argv[argbase + 1]); 114 | break; 115 | case 2: 116 | if (fmtByteOrder == 0) 117 | { 118 | // read number of files 119 | UINT16 valLE = ReadLE16(&inData[0x00]); 120 | UINT16 valBE = ReadBE16(&inData[0x00]); 121 | // the value with the correct order is smaller 122 | fmtByteOrder = (valLE < valBE) ? BO_LE : BO_BE; 123 | printf("Detected byte order: %s Endian\n", (fileFmt == BO_LE) ? "Little" : "Big"); 124 | } 125 | ExtractArchive(inLen, inData, argv[argbase + 1]); 126 | break; 127 | default: 128 | printf("Unknown format!\n"); 129 | break; 130 | } 131 | 132 | free(inData); 133 | 134 | return 0; 135 | } 136 | 137 | static void DecompressFile(const UINT8* inData, const char* fileName) 138 | { 139 | UINT32 comprSize; 140 | UINT32 decSize; 141 | UINT8* decBuffer; 142 | UINT32 outSize; 143 | FILE* hFile; 144 | 145 | comprSize = ReadUInt32(&inData[0x00]); 146 | decSize = ReadUInt32(&inData[0x04]); 147 | printf("Compressed: %u bytes, decompressed: %u bytes\n", comprSize, decSize); 148 | decBuffer = (UINT8*)malloc(decSize); 149 | outSize = LZSS_Decode(comprSize, &inData[0x08], decSize, decBuffer); 150 | if (outSize != decSize) 151 | printf("Warning - not all data was decompressed!\n"); 152 | 153 | hFile = fopen(fileName, "wb"); 154 | if (hFile == NULL) 155 | { 156 | free(decBuffer); 157 | printf("Error writing %s!\n", fileName); 158 | return; 159 | } 160 | fwrite(decBuffer, 1, outSize, hFile); 161 | fclose(hFile); 162 | free(decBuffer); 163 | 164 | return; 165 | } 166 | 167 | static void DecompressMultiFile(UINT32 arcSize, const UINT8* arcData, const char* fileName) 168 | { 169 | const char* fileExt; 170 | char* outName; 171 | char* outExt; 172 | UINT32 curPos; 173 | UINT32 cmpSize; 174 | UINT32 fileCnt; 175 | UINT32 curFile; 176 | 177 | // detect number of files 178 | fileCnt = 0; 179 | for (curPos = 0x00; curPos < arcSize; fileCnt ++) 180 | { 181 | cmpSize = ReadUInt32(&arcData[curPos + 0x00]); 182 | curPos += 0x08 + cmpSize; 183 | } 184 | //printf("Detected %u %s.\n", fileCnt, (fileCnt == 1) ? "file" : "files"); 185 | 186 | fileExt = strrchr(fileName, '.'); 187 | if (fileExt == NULL) 188 | fileExt = fileName + strlen(fileName); 189 | outName = (char*)malloc(strlen(fileName) + 0x10); 190 | strcpy(outName, fileName); 191 | outExt = outName + (fileExt - fileName); 192 | 193 | // extract everything 194 | curPos = 0x00; 195 | for (curFile = 0; curFile < fileCnt; curFile ++) 196 | { 197 | cmpSize = ReadUInt32(&arcData[curPos + 0x00]); 198 | 199 | // generate file name(ABC.ext -> ABC_00.ext) 200 | if (fileCnt > 1) 201 | sprintf(outExt, "_%02X%s", curFile, fileExt); 202 | 203 | printf("File %u / %u: offset: 0x%06X\n ", 1 + curFile, fileCnt, curPos); 204 | DecompressFile(&arcData[curPos], outName); 205 | curPos += 0x08 + cmpSize; 206 | } 207 | 208 | return; 209 | } 210 | 211 | static void ExtractArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 212 | { 213 | const char* fileExt; 214 | char* outName; 215 | char* outExt; 216 | FILE* hFile; 217 | UINT32 curPos; 218 | UINT32 filePos; 219 | UINT32 fileSize; 220 | UINT32 fileCnt; 221 | UINT32 curFile; 222 | 223 | fileCnt = ReadUInt16(&arcData[0x00]); 224 | 225 | fileExt = strrchr(fileName, '.'); 226 | if (fileExt == NULL) 227 | fileExt = fileName + strlen(fileName); 228 | outName = (char*)malloc(strlen(fileName) + 0x10); 229 | strcpy(outName, fileName); 230 | outExt = outName + (fileExt - fileName); 231 | 232 | // extract everything 233 | curPos = 0x02; 234 | for (curFile = 0; curFile < fileCnt; curFile ++, curPos += 0x08) 235 | { 236 | filePos = ReadUInt32(&arcData[curPos + 0x00]); 237 | fileSize = ReadUInt32(&arcData[curPos + 0x04]); 238 | 239 | // generate file name(ABC.ext -> ABC_00.ext) 240 | if (fileCnt > 1) 241 | sprintf(outExt, "_%02X%s", curFile, fileExt); 242 | 243 | // The actual file data may or may not be compressed. 244 | // (Only the game code knows whether or not it is compressed.) 245 | printf("File %u / %u: offset: 0x%06X, size 0x%04X\n", 1 + curFile, fileCnt, filePos, fileSize); 246 | hFile = fopen(outName, "wb"); 247 | if (hFile == NULL) 248 | { 249 | printf("Error writing %s!\n", outName); 250 | continue; 251 | } 252 | fwrite(&arcData[filePos], 1, fileSize, hFile); 253 | fclose(hFile); 254 | } 255 | 256 | return; 257 | } 258 | 259 | // LZSS decoder by Haruhiko Okumura, 1989-04-06 260 | // modified to work with memory instead of file streams 261 | 262 | #define N 4096 /* size of ring buffer */ 263 | #define F 18 /* upper limit for match_length */ 264 | #define THRESHOLD 2 /* encode string into position and length 265 | if match_length is greater than this */ 266 | 267 | static void LZSS_BufInit(UINT8* text_buf) 268 | { 269 | // Important Note: These are non-standard values and ARE used by the compressed data. 270 | UINT16 bufPos; 271 | UINT16 regD0; 272 | UINT16 regD1; 273 | 274 | // LZSS table initialization from Arcus Odyssey X68000, M_DRV.X 275 | // decompression routine: file offset 0x020C, executed from 0x03593C 276 | bufPos = 0x0000; 277 | // 035946 - 000..CFF (0x0D bytes of 00, 01, 02, ... FF each) 278 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++) 279 | { 280 | for (regD1 = 0x00; regD1 < 0x0D; regD1 ++, bufPos ++) 281 | text_buf[bufPos] = (UINT8)regD0; 282 | } 283 | // 035954 - AD00..ADFF (00 .. FF) 284 | for (regD0 = 0x00; regD0 < 0x100; regD0 ++, bufPos ++) 285 | text_buf[bufPos] = (UINT8)regD0; 286 | // 03595A - AE00..AEFF (FF .. 00) 287 | do 288 | { 289 | regD0 --; 290 | text_buf[bufPos] = (UINT8)regD0; 291 | bufPos ++; 292 | } while(regD0 > 0x00); 293 | // 035960 - AF00..AF7F (0x80 times 00) 294 | for (regD0 = 0x00; regD0 < 0x80; regD0 ++, bufPos ++) 295 | text_buf[bufPos] = 0x00; 296 | // 035968 - AF80..AFED (0x6E times 20/space) 297 | for (regD0 = 0x00; regD0 < 0x80 - F; regD0 ++, bufPos ++) 298 | text_buf[bufPos] = ' '; 299 | 300 | return; 301 | } 302 | 303 | UINT32 LZSS_Decode(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData) 304 | { 305 | UINT32 inPos, outPos; 306 | UINT8 text_buf[N]; /* ring buffer of size N, 307 | with extra F-1 bytes to facilitate string comparison */ 308 | int i, j, k, r, c; 309 | unsigned int flags; 310 | 311 | //for (i = 0; i < N - F; i++) text_buf[i] = 0x00; 312 | LZSS_BufInit(text_buf); 313 | r = N - F; flags = 0; 314 | inPos = outPos = 0; 315 | while(inPos < inLen && outPos < outLen) { 316 | if (((flags >>= 1) & 256) == 0) { 317 | c = inData[inPos++]; 318 | flags = c | 0xff00; /* uses higher byte cleverly */ 319 | } /* to count eight */ 320 | if (flags & 1) { 321 | if (inPos >= inLen) break; 322 | c = inData[inPos++]; 323 | outData[outPos++] = c; text_buf[r++] = c; r &= (N - 1); 324 | } else { 325 | if (inPos + 1 >= inLen) break; 326 | i = inData[inPos++]; 327 | j = inData[inPos++]; 328 | i |= ((j & 0xf0) << 4); j = (j & 0x0f) + THRESHOLD; 329 | for (k = 0; k <= j; k++) { 330 | c = text_buf[(i + k) & (N - 1)]; 331 | if (outPos >= outLen) break; 332 | outData[outPos++] = c; text_buf[r++] = c; r &= (N - 1); 333 | } 334 | } 335 | } 336 | return outPos; 337 | } 338 | 339 | static UINT16 ReadLE16(const UINT8* data) 340 | { 341 | return (data[0x00] << 0) | (data[0x01] << 8); 342 | } 343 | 344 | static UINT16 ReadBE16(const UINT8* data) 345 | { 346 | return (data[0x00] << 8) | (data[0x01] << 0); 347 | } 348 | 349 | static UINT16 ReadUInt16(const UINT8* data) 350 | { 351 | return (fmtByteOrder == BO_LE) ? ReadLE16(data) : ReadBE16(data); 352 | } 353 | 354 | static UINT32 ReadLE32(const UINT8* data) 355 | { 356 | return (data[0x00] << 0) | (data[0x01] << 8) | 357 | (data[0x02] << 16) | (data[0x03] << 24); 358 | } 359 | 360 | static UINT32 ReadBE32(const UINT8* data) 361 | { 362 | return (data[0x00] << 24) | (data[0x01] << 16) | 363 | (data[0x02] << 8) | (data[0x03] << 0); 364 | } 365 | 366 | static UINT32 ReadUInt32(const UINT8* data) 367 | { 368 | return (fmtByteOrder == BO_LE) ? ReadLE32(data) : ReadBE32(data); 369 | } 370 | -------------------------------------------------------------------------------- /x68k_sps_dec.c: -------------------------------------------------------------------------------- 1 | // X68000 S.P.S. Archive Unpacker 2 | // ------------------------------ 3 | // Valley Bell, written on 2017-09-01 (Street Fighter II: Champion Edition) 4 | // updated on 2017-09-02 (Daimakaimura) 5 | // updated on 2017-09-03 (Super Street Fighter II: The New Challengers) 6 | // based on Twinkle Soft Decompressor 7 | 8 | // BLK format 9 | // ---------- 10 | // Format: 11 | // repeat N times: 12 | // 4 bytes - file offset 13 | // 4 bytes - file size 14 | // TOC ends where the first file starts 15 | // 16 | // Games: 17 | // - Street Fighter II: Champion Edition 18 | // C_SE.BLK - uncompressed 19 | // FM.BLK / GM.BLK - compressed with LZSS_SPS_V1 20 | // - Super Street Fighter II: The New Challengers BLK format 21 | // FM.BLK / GM.BLK - compressed with LZSS_SPS_V2 22 | 23 | // SLD format 24 | // ---------- 25 | // Format: 26 | // repeat N-1 times: 27 | // 2 bytes - size of file 1 28 | // This is just a list of (n-1) file sizes. 29 | // 30 | // Games: 31 | // - Daimakaimura 32 | // TEXTDAT2.SLD / TEXTDAT4.SLD - compressed with LZSS_SPS_V1 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #include "stdtype.h" 39 | 40 | 41 | #ifdef _MSC_VER 42 | #define stricmp _stricmp 43 | #else 44 | #define stricmp strcasecmp 45 | #endif 46 | 47 | 48 | static UINT8 WriteFileData(UINT32 dataLen, const UINT8* data, const char* fileName); 49 | static void DecompressFile(UINT32 inSize, const UINT8* inData, const char* fileName); 50 | static void ExtractBLKArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 51 | static void ExtractSLDArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName); 52 | static UINT32 LZSS_Decode_v1(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData); 53 | static UINT32 LZSS_Decode_v2(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData); 54 | static UINT16 ReadBE16(const UINT8* Data); 55 | static UINT32 ReadBE32(const UINT8* Data); 56 | 57 | 58 | #define ARC_AUTO 0xFF 59 | #define ARC_BLK 0x00 60 | #define ARC_SLD 0x01 61 | 62 | #define LZSS_AUTO 0xFF // automatic detection 63 | #define LZSS_NONE 0x00 // no compression 64 | #define LZSS_SPS_V1 0x01 // Daimakaimura, Street Fighter II: CE 65 | #define LZSS_SPS_V2 0x02 // Super Street Fighter II: TNC 66 | 67 | static const char* ARCHIVE_STRS[] = 68 | { 69 | "BLK", 70 | "SLD", 71 | }; 72 | 73 | static const char* COMPR_STRS[] = 74 | { 75 | "none", 76 | "LZSS-SPS v1", 77 | "LZSS-SPS v2", 78 | }; 79 | 80 | static UINT8 ArchiveType; 81 | static UINT8 ComprType; 82 | 83 | int main(int argc, char* argv[]) 84 | { 85 | int argbase; 86 | FILE* hFile; 87 | UINT32 inLen; 88 | UINT8* inData; 89 | 90 | printf("X68000 S.P.S. Archive Unpacker\n------------------------------\n"); 91 | if (argc < 3) 92 | { 93 | printf("Usage: x68k_sps_dec.exe [Options] input.blk output.bin\n"); 94 | printf("This will create files output_00.bin, output_01.bin, etc.\n"); 95 | printf("\n"); 96 | printf("Options:\n"); 97 | printf(" -s SLD archive\n"); 98 | printf(" -b BLK archive\n"); 99 | printf(" -r extract raw data\n"); 100 | printf(" -1 decompress using LZSS-SPS variant 1\n"); 101 | printf(" -2 decompress using LZSS-SPS variant 2\n"); 102 | printf("\n"); 103 | printf("Supported games:\n"); 104 | printf(" Daimakaimura (SLD archive, LZSS v1)\n"); 105 | printf(" Street Fighter II: Champion Edition (BLK archive, LZSS v1)\n"); 106 | printf(" Super Street Fighter II: The New Challengers (BLK archive, LZSS v2)\n"); 107 | return 0; 108 | } 109 | 110 | ArchiveType = ARC_AUTO; 111 | ComprType = LZSS_AUTO; 112 | argbase = 1; 113 | while(argbase < argc && argv[argbase][0] == '-') 114 | { 115 | if (argv[argbase][1] == 's') 116 | { 117 | ArchiveType = ARC_SLD; 118 | } 119 | else if (argv[argbase][1] == 'b') 120 | { 121 | ArchiveType = ARC_BLK; 122 | } 123 | else if (argv[argbase][1] == 'r') 124 | { 125 | ComprType = LZSS_NONE; 126 | } 127 | else if (argv[argbase][1] == '1') 128 | { 129 | ComprType = LZSS_SPS_V1; 130 | } 131 | else if (argv[argbase][1] == '2') 132 | { 133 | ComprType = LZSS_SPS_V2; 134 | } 135 | else 136 | break; 137 | argbase ++; 138 | } 139 | if (argc < argbase + 2) 140 | { 141 | printf("Insufficient parameters!\n"); 142 | return 0; 143 | } 144 | 145 | if (ArchiveType == ARC_AUTO) 146 | { 147 | const char* fileExt; 148 | 149 | fileExt = strrchr(argv[argbase + 0], '.'); 150 | if (fileExt != NULL) 151 | { 152 | fileExt ++; 153 | if (! stricmp(fileExt, "BLK")) 154 | ArchiveType = ARC_BLK; 155 | else if (! stricmp(fileExt, "SLD")) 156 | ArchiveType = ARC_SLD; 157 | } 158 | } 159 | if (ArchiveType == ARC_AUTO) 160 | { 161 | printf("Unknown archive type! Please specify the archive type manually\n"); 162 | return 2; 163 | } 164 | printf("Archive format: %s\n", ARCHIVE_STRS[ArchiveType]); 165 | 166 | hFile = fopen(argv[argbase + 0], "rb"); 167 | if (hFile == NULL) 168 | return 1; 169 | 170 | fseek(hFile, 0, SEEK_END); 171 | inLen = ftell(hFile); 172 | if (inLen > 0x100000) 173 | inLen = 0x100000; // limit to 1 MB 174 | 175 | inData = (UINT8*)malloc(inLen); 176 | fseek(hFile, 0, SEEK_SET); 177 | fread(inData, 0x01, inLen, hFile); 178 | 179 | fclose(hFile); 180 | 181 | if (ArchiveType == ARC_BLK) 182 | ExtractBLKArchive(inLen, inData, argv[argbase + 1]); 183 | else if (ArchiveType == ARC_SLD) 184 | ExtractSLDArchive(inLen, inData, argv[argbase + 1]); 185 | 186 | free(inData); 187 | 188 | return 0; 189 | } 190 | 191 | static UINT8 WriteFileData(UINT32 dataLen, const UINT8* data, const char* fileName) 192 | { 193 | FILE* hFile; 194 | 195 | hFile = fopen(fileName, "wb"); 196 | if (hFile == NULL) 197 | { 198 | printf("Error writing %s!\n", fileName); 199 | return 0xFF; 200 | } 201 | 202 | fwrite(data, 0x01, dataLen, hFile); 203 | fclose(hFile); 204 | 205 | return 0x00; 206 | } 207 | 208 | static void DecompressFile(UINT32 inSize, const UINT8* inData, const char* fileName) 209 | { 210 | UINT32 decSize; 211 | UINT8* decBuffer; 212 | UINT32 outSize; 213 | 214 | if (ComprType == LZSS_NONE) 215 | { 216 | WriteFileData(inSize, inData, fileName); 217 | return; 218 | } 219 | 220 | decSize = 0x100000; // 1 MB should be more than enough 221 | //printf("Compressed: %u bytes, decompressed: %u bytes\n", inSize, decSize); 222 | decBuffer = (UINT8*)malloc(decSize); 223 | if (ComprType == LZSS_SPS_V1) 224 | outSize = LZSS_Decode_v1(inSize, inData, decSize, decBuffer); 225 | else if (ComprType == LZSS_SPS_V2) 226 | outSize = LZSS_Decode_v2(inSize, inData, decSize, decBuffer); 227 | else 228 | { 229 | memcpy(decBuffer, inData, inSize); 230 | outSize = inSize; 231 | } 232 | if (outSize >= decSize) 233 | printf("Warning - not all data was decompressed!\n"); 234 | 235 | WriteFileData(outSize, decBuffer, fileName); 236 | 237 | free(decBuffer); decBuffer = NULL; 238 | 239 | return; 240 | } 241 | 242 | static void ExtractBLKArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 243 | { 244 | const char* fileExt; 245 | char* outName; 246 | char* outExt; 247 | UINT32 filePos; 248 | UINT32 fileSize; 249 | UINT32 fileCnt; 250 | UINT32 curFile; 251 | UINT32 arcPos; 252 | UINT32 minPos; 253 | UINT8 firstByte; 254 | 255 | // detect number of files 256 | fileCnt = 0; 257 | firstByte = 0xFF; 258 | minPos = arcSize; 259 | for (arcPos = 0x00; arcPos < minPos; arcPos += 0x08, fileCnt ++) 260 | { 261 | filePos = ReadBE32(&arcData[arcPos + 0x00]); 262 | if (filePos < minPos) 263 | minPos = filePos; 264 | fileSize = ReadBE32(&arcData[arcPos + 0x04]); 265 | if (filePos + fileSize > arcSize) 266 | break; 267 | // Assume that every compressed file starts with an FF byte. (for LZSS SPS v1) 268 | // (might fail if there are lots of repeated bytes at the beginning) 269 | if (filePos < arcSize) 270 | firstByte &= arcData[filePos]; 271 | } 272 | if (ComprType == LZSS_AUTO) 273 | { 274 | if (firstByte == 0xFF) 275 | ComprType = LZSS_SPS_V1; 276 | else if (firstByte == 0xFA) // MIDIs in LZSS SPS v2 begin with this 277 | ComprType = LZSS_SPS_V2; 278 | else 279 | ComprType = LZSS_NONE; 280 | } 281 | printf("Compression: %s\n", COMPR_STRS[ComprType]); 282 | 283 | fileExt = strrchr(fileName, '.'); 284 | if (fileExt == NULL) 285 | fileExt = fileName + strlen(fileName); 286 | outName = (char*)malloc(strlen(fileName) + 0x10); 287 | strcpy(outName, fileName); 288 | outExt = outName + (fileExt - fileName); 289 | 290 | // extract everything 291 | arcPos = 0x00; 292 | for (curFile = 0; curFile < fileCnt; curFile ++, arcPos += 0x08) 293 | { 294 | filePos = ReadBE32(&arcData[arcPos + 0x00]); 295 | fileSize = ReadBE32(&arcData[arcPos + 0x04]); 296 | 297 | // generate file name(ABC.ext -> ABC_00.ext) 298 | sprintf(outExt, "_%02X%s", curFile, fileExt); 299 | 300 | printf("file %u / %u\n", 1 + curFile, fileCnt); 301 | DecompressFile(fileSize, &arcData[filePos], outName); 302 | } 303 | 304 | return; 305 | } 306 | 307 | static void ExtractSLDArchive(UINT32 arcSize, const UINT8* arcData, const char* fileName) 308 | { 309 | const char* fileExt; 310 | char* outName; 311 | char* outExt; 312 | UINT32 filePos; 313 | UINT32 fileSize; 314 | UINT32 fileCnt; 315 | UINT32 curFile; 316 | UINT32 arcPos; 317 | UINT32 minPos; 318 | 319 | // detect number of files 320 | fileCnt = 0; 321 | minPos = arcSize; 322 | filePos = 0x00; 323 | for (arcPos = 0x00; arcPos < minPos; arcPos += 0x02, fileCnt ++) 324 | { 325 | fileSize = ReadBE16(&arcData[arcPos]); 326 | filePos += fileSize; 327 | if (arcPos + filePos >= arcSize) 328 | { 329 | fileCnt ++; 330 | break; 331 | } 332 | } 333 | if (ComprType == LZSS_AUTO) 334 | ComprType = LZSS_SPS_V1; 335 | printf("Compression: %s\n", COMPR_STRS[ComprType]); 336 | 337 | fileExt = strrchr(fileName, '.'); 338 | if (fileExt == NULL) 339 | fileExt = fileName + strlen(fileName); 340 | outName = (char*)malloc(strlen(fileName) + 0x10); 341 | strcpy(outName, fileName); 342 | outExt = outName + (fileExt - fileName); 343 | 344 | // extract everything 345 | arcPos = 0x00; 346 | filePos = (fileCnt - 0x01) * 0x02; 347 | for (curFile = 0; curFile < fileCnt; curFile ++, arcPos += 0x02) 348 | { 349 | if (arcPos < (fileCnt - 0x01) * 0x02) 350 | fileSize = ReadBE16(&arcData[arcPos]); 351 | else 352 | fileSize = arcSize - filePos; 353 | if (filePos + fileSize > arcSize) 354 | fileSize = arcSize - filePos; 355 | 356 | // generate file name(ABC.ext -> ABC_00.ext) 357 | sprintf(outExt, "_%02X%s", curFile, fileExt); 358 | 359 | printf("file %u / %u\n", 1 + curFile, fileCnt); 360 | DecompressFile(fileSize, &arcData[filePos], outName); 361 | filePos += fileSize; 362 | } 363 | 364 | return; 365 | } 366 | 367 | // original LZSS decoder by Haruhiko Okumura, 1989-04-06 368 | // This is a modified version that doesn't use a ring buffer. 369 | // Instead output data is referenced directly. 370 | 371 | static UINT32 LZSS_Decode_v1(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData) 372 | { 373 | UINT32 inPos, outPos; 374 | unsigned int i, j, k; 375 | unsigned int flags, fbits; 376 | 377 | flags = 0; fbits = 1; 378 | inPos = outPos = 0; 379 | while(inPos < inLen && outPos < outLen) { 380 | flags <<= 1; fbits --; 381 | if (!fbits) { 382 | flags = inData[inPos++]; 383 | fbits = 8; 384 | } 385 | if (flags & 0x80) { 386 | if (inPos >= inLen) break; 387 | outData[outPos++] = inData[inPos++]; 388 | } else { 389 | if (inPos + 1 >= inLen) break; 390 | j = inData[inPos++]; 391 | i = inData[inPos++]; 392 | i |= ((j & 0xf0) << 4); j = (j & 0x0f) + 2; 393 | if (i > outPos) 394 | { 395 | printf("Decompression Error at 0x%06X: Accessing out-of-bounds data!\n", inPos - 2); 396 | break; 397 | } 398 | for (k = 0; k <= j; k++) { 399 | if (outPos >= outLen) break; 400 | outData[outPos++] = outData[outPos - i]; 401 | } 402 | } 403 | } 404 | return outPos; 405 | } 406 | 407 | // custom LZSS variant used in Super Street Fighter II: The New Challengers 408 | // The decompression routine is stored in X68030 RAM at 0919C8-091A30. (decompressed from SP2.X) 409 | static UINT32 LZSS_Decode_v2(UINT32 inLen, const UINT8* inData, UINT32 outLen, UINT8* outData) 410 | { 411 | UINT32 inPos, outPos; 412 | unsigned int i, j, k; 413 | unsigned int flags, fbits; 414 | 415 | flags = 0; fbits = 1; 416 | inPos = outPos = 0; 417 | while(inPos < inLen && outPos < outLen) { 418 | flags <<= 1; fbits --; 419 | if (!fbits) { 420 | flags = inData[inPos++]; 421 | fbits = 8; 422 | } 423 | if (flags & 0x80) { 424 | if (inPos >= inLen) break; 425 | outData[outPos++] = inData[inPos++]; 426 | } else { 427 | if (inPos + 1 >= inLen) break; 428 | 429 | flags <<= 1; fbits --; 430 | if (!fbits) { 431 | flags = inData[inPos++]; 432 | fbits = 8; 433 | } 434 | if (! (flags & 0x80)) 435 | { 436 | // 0919E8 437 | i = inData[inPos++]; 438 | j = i & 7; 439 | if (j == 0) 440 | { 441 | // 0919F0 442 | j = inData[inPos++]; 443 | if (j == 0) 444 | break; // data end 445 | j --; 446 | } 447 | i = (i & 0xF8) << 5; 448 | i |= inData[inPos++]; 449 | i = 0x2000 - i; 450 | } 451 | else 452 | { 453 | // 091A06 454 | flags <<= 1; fbits --; 455 | if (!fbits) { 456 | flags = inData[inPos++]; 457 | fbits = 8; 458 | } 459 | j = (flags & 0x80) >> 5; 460 | flags <<= 1; fbits --; 461 | if (!fbits) { 462 | flags = inData[inPos++]; 463 | fbits = 8; 464 | } 465 | j |= (flags & 0x80) >> 6; 466 | flags <<= 1; fbits --; 467 | if (!fbits) { 468 | flags = inData[inPos++]; 469 | fbits = 8; 470 | } 471 | j |= (flags & 0x80) >> 7; 472 | j ++; 473 | i = inData[inPos++]; 474 | i = 0x100 - i; 475 | } 476 | if (i > outPos) 477 | { 478 | printf("Decompression Error at 0x%06X: Accessing out-of-bounds data!\n", inPos - 2); 479 | break; 480 | } 481 | for (k = 0; k <= j; k++) { 482 | if (outPos >= outLen) break; 483 | outData[outPos++] = outData[outPos - i]; 484 | } 485 | } 486 | } 487 | return outPos; 488 | } 489 | 490 | static UINT16 ReadBE16(const UINT8* Data) 491 | { 492 | return (Data[0x00] << 8) | (Data[0x01] << 0); 493 | } 494 | 495 | static UINT32 ReadBE32(const UINT8* Data) 496 | { 497 | return (Data[0x00] << 24) | (Data[0x01] << 16) | 498 | (Data[0x02] << 8) | (Data[0x03] << 0); 499 | } 500 | -------------------------------------------------------------------------------- /xordec.c: -------------------------------------------------------------------------------- 1 | // XOR Decoder 2 | // ----------- 3 | // Valley Bell, written on 2021-12-22 4 | #include 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | #include 9 | #endif 10 | 11 | typedef unsigned char UINT8; 12 | 13 | 14 | int main(int argc, char* argv[]) 15 | { 16 | int argbase; 17 | FILE* hFile; 18 | size_t inLen; 19 | UINT8* inData; 20 | UINT8 key; 21 | #ifdef _WIN32 22 | FILETIME ftWrite; 23 | HANDLE hWinFile; 24 | #endif 25 | 26 | printf("XOR Decoder\n-----------\n"); 27 | if (argc < 3) 28 | { 29 | printf("Usage: %s key input.bin output.bin\n", argv[0]); 30 | return 0; 31 | } 32 | argbase = 1; 33 | /*while(argbase < argc && argv[argbase][0] == '-') 34 | { 35 | if (argv[argbase][1] == '\0') 36 | { 37 | } 38 | else 39 | break; 40 | argbase ++; 41 | }*/ 42 | if (argc < argbase + 3) 43 | { 44 | printf("Insufficient parameters!\n"); 45 | return 0; 46 | } 47 | 48 | key = (UINT8)strtoul(argv[argbase + 0], NULL, 0); 49 | 50 | hFile = fopen(argv[argbase + 1], "rb"); 51 | if (hFile == NULL) 52 | return 1; 53 | 54 | fseek(hFile, 0, SEEK_END); 55 | inLen = ftell(hFile); 56 | if (inLen > 0x1000000) 57 | inLen = 0x1000000; // limit to 16 MB 58 | 59 | inData = (UINT8*)malloc(inLen); 60 | fseek(hFile, 0, SEEK_SET); 61 | fread(inData, 0x01, inLen, hFile); 62 | 63 | fclose(hFile); 64 | 65 | #ifdef _WIN32 66 | ftWrite.dwLowDateTime = ftWrite.dwHighDateTime = 0; 67 | hWinFile = CreateFile(argv[argbase + 1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 68 | if (hWinFile != INVALID_HANDLE_VALUE) 69 | { 70 | GetFileTime(hWinFile, NULL, NULL, &ftWrite); 71 | CloseHandle(hWinFile); 72 | } 73 | #endif 74 | 75 | { 76 | size_t curPos; 77 | size_t startPos = 0x00; 78 | size_t endPos = inLen; 79 | //if (inLen >= 0x8000) 80 | // endPos = inLen - 0x8000; 81 | 82 | for (curPos = startPos; curPos < endPos; curPos ++) 83 | inData[curPos] ^= key; 84 | //inData[curPos] = key - inData[curPos]; 85 | 86 | hFile = fopen(argv[argbase + 2], "wb"); 87 | if (hFile == NULL) 88 | { 89 | printf("Error writing %s!\n", argv[argbase + 2]); 90 | } 91 | else 92 | { 93 | fwrite(inData, 1, inLen, hFile); 94 | fclose(hFile); 95 | } 96 | } 97 | 98 | #ifdef _WIN32 99 | if (ftWrite.dwLowDateTime != 0 && ftWrite.dwHighDateTime != 0) 100 | { 101 | hWinFile = CreateFile(argv[argbase + 2], GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 102 | if (hWinFile != INVALID_HANDLE_VALUE) 103 | { 104 | SetFileTime(hWinFile, NULL, NULL, &ftWrite); 105 | CloseHandle(hWinFile); 106 | } 107 | } 108 | #endif 109 | 110 | free(inData); 111 | 112 | return 0; 113 | } 114 | --------------------------------------------------------------------------------