// // Utility for (de)compressing Alter Ego (1986) MS-DOS data files // By Matti 'ccr' Hamalainen // // The game uses a fixed dictionary compression approach. // // Works full roundtrip, but does not produce identically compressed // data probably due to algorithmic differences. // // cc -W -Wall -std=c99 -o aego_decomp aego_decomp.c // #include #include #include #include #define ENABLE_DEBUG 0 // Enable debug information #define DICT_MAX_SIZE 256 // Max entries in the dictionary (one byte) #define DICT_WORD_LEN 16 // Max dictionary entry length #if ENABLE_DEBUG #define DBG(...) do { fprintf(stderr, __VA_ARGS__); } while (0) static char outBuf[256]; static const char * convStr(const char *str, size_t maxLen) { size_t outOffs = 0; for (size_t srcOffs = 0; (maxLen <= 0 || srcOffs < maxLen) && str[srcOffs]; srcOffs++) { if (isprint(str[srcOffs])) { outBuf[outOffs++] = str[srcOffs]; } else { sprintf(outBuf + outOffs, "\\x%02x", str[srcOffs]); outOffs += 4; } } outBuf[outOffs] = 0; return (char *) outBuf; } #else #define DBG(...) #endif int main(int argc, char *argv[]) { if (argc < 3) { printf( "Compress and decompress Alter Ego (1986, MS-DOS) data files\n" "Usage: %s [filename]\n", argv[0]); return 1; } int operMode; char * dictFilename = argv[2]; char * dataFilename = argc >= 3 ? argv[3] : NULL; switch (tolower(argv[1][0])) { case 'd': operMode = 0; break; case 'c': operMode = 1; break; default: printf("Unknown operating mode '%s'.\n", argv[1]); return 1; } FILE *inFH = NULL; int dictSize = 0; char dictData[DICT_MAX_SIZE][DICT_WORD_LEN]; size_t dictDataLen[DICT_MAX_SIZE]; memset(dictData, 0, sizeof(dictData)); if (operMode < 2) { if ((inFH = fopen(dictFilename, "rb")) == NULL) { printf("Could not open '%s'.\n", dictFilename); goto out; } // Parse the dictionary file bool isEOF = false; while (!isEOF) { // Read one line to buffer char inBuf[64]; size_t bufOffs = 0; do { int value = fgetc(inFH); if (value == EOF || value == 0x1a) { isEOF = true; break; } else if (value == '\n') break; if (bufOffs + 1 >= DICT_WORD_LEN) { printf("Error in dictionary file, line longer than expected.\n"); goto out; } inBuf[bufOffs++] = value; } while (1); inBuf[bufOffs] = 0; // Trim line ending size_t len = bufOffs; while (len > 0) { if (inBuf[len - 1] == '\n' || inBuf[len - 1] == '\r') { inBuf[len - 1] = 0; len--; } else break; } // Check if we fit if (dictSize >= DICT_MAX_SIZE - 1) { printf("Error in dictionary file, too many entries.\n"); goto out; } if (len >= DICT_WORD_LEN) { printf("Error in dictionary file, entry too long\n"); goto out; } // Add entry to dictionary //fprintf(stderr, "DICT: %02x \"%s\" %d/%02x\n", dictSize, inBuf, dictSize >= 0x80 ? dictSize - 0x80 : dictSize, dictSize >= 0x80 ? dictSize - 0x80 : dictSize); strcpy(dictData[dictSize], inBuf); dictDataLen[dictSize] = len; dictSize++; } fclose(inFH); } // Open datafile or use stdin if (dataFilename != NULL) { if ((inFH = fopen(dataFilename, "rb")) == NULL) { printf("Could not open '%s'.\n", dataFilename); goto out; } } else { inFH = stdin; } if (operMode == 0) { // Decompress data int value; while ((value = fgetc(inFH)) != EOF) { int index = value & 0x80 ? value & 0x7f : value + 0x80; if (value && dictData[index][0]) { fputs(dictData[index], stdout); } else { fputc(value, stdout); } } } else if (operMode == 1) { // Compress data char dataBuf[DICT_WORD_LEN + 2]; size_t dataAvail = 0; while (1) { // Read some data into buffer if (dataAvail < DICT_WORD_LEN) { DBG("FREAD\n"); dataAvail += fread(dataBuf + dataAvail, sizeof(dataBuf[0]), DICT_WORD_LEN - dataAvail, inFH); } if (!dataAvail) { DBG("END OF STREAM\n"); break; } dataBuf[dataAvail] = 0; DBG("BUF %zu: '%s'\n", dataAvail, convStr(dataBuf, 0)); if (dataAvail >= 2) { // Check dictionary for longest possible match int matchID = 0; size_t matchLen = 0; for (int index = 0; index < dictSize; index++) { if (dictDataLen[index] <= dataAvail && dictDataLen[index] > matchLen && memcmp(dataBuf, dictData[index], dictDataLen[index]) == 0) { matchID = index; matchLen = dictDataLen[index]; } } if (matchLen) { DBG(" - match: %d '%s' (%zu) in '%s'\n", matchID, dictData[matchID], matchLen, convStr(dataBuf, 0)); if (matchID < 0x80) fputc(0x80 + matchID, stdout); else fputc(matchID - 0x80, stdout); if (matchLen < dataAvail) { memmove(dataBuf, dataBuf + matchLen, dataAvail - matchLen); } dataAvail -= matchLen; } else { DBG(" - no match: '%s'\n", convStr(dataBuf, 1)); fputc(dataBuf[0], stdout); memmove(dataBuf, dataBuf + 1, dataAvail); dataAvail -= 1; } } else { DBG(" - end of data %zu: '%s'\n", dataAvail, convStr(dataBuf, 0)); fputs(dataBuf, stdout); dataAvail = 0; } } } out: if (inFH != NULL) fclose(inFH); return 0; }