Добро пожаловать, гость ( Вход | Регистрация )
![]() ![]() |
| Lumis |
Mar 9 2026, 15:47
Сообщение
#1
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Привет. По просьбе -=CHE@TER=- перегнал в ANSI C и протестировал на EPF от Aladdin 1996 с помощью chatgpt оригинальный код распаковщика ASM + C. В его тестовой среде в докере и у меня на винде отрабатывает чисто.
CODE #include <stdio.h> #include <stdlib.h> #include <string.h> #if defined(_MSC_VER) && (_MSC_VER < 1600) typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #else #include <stdint.h> #endif #define EPFS_MAGIC 0x53465045UL #define EPFS_HEADER_SIZE 11 #define EPFS_ITEM_SIZE 22 #define LZW_FIRST_CODE 256 #define LZW_MAX_BITS 14 #define LZW_DICT_CAP 0x4680u /* from original buffers: 0x8D00 bytes / 2 */ #define LZW_STACK_CAP 0x0FA0u /* original hard stop */ typedef struct epfs_item_tag { char name[14]; uint8_t packed; uint32_t packed_size; uint32_t unpacked_size; } epfs_item; static uint16_t rd16le(const uint8_t *p) { return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); } static uint32_t rd32le(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } static int read_fully(FILE *fp, void *buf, size_t size) { return fread(buf, 1, size, fp) == size; } static int write_fully(FILE *fp, const void *buf, size_t size) { return fwrite(buf, 1, size, fp) == size; } static void sanitize_name(char *s) { char *p = s; while (*p) { unsigned char c = (unsigned char)*p; if (c < 32 || c == ':' || c == '\\' || c == '/' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') { *p = '_'; } ++p; } if (s[0] == '\0') { strcpy(s, "NONAME.BIN"); } } /* * This is not textbook LZW. * * The reset code is (maxvalue - 1), end code is maxvalue. * The key quirk, matching the original game code: * reset clears nextcode back to 256, but DOES NOT reset bit width to 9. * * If you "fix" that into normal LZW, several real ALADDIN.EPF members fail * with bogus recursive chains and depth overflow. */ static int unlzw_epfs(uint32_t out_size, const uint8_t *src, uint32_t src_size, uint8_t *dst) { uint16_t prefix[LZW_DICT_CAP]; uint8_t suffix[LZW_DICT_CAP]; uint8_t stack[LZW_STACK_CAP]; const uint8_t *sp; const uint8_t *src_end; uint8_t *dp; uint8_t *dst_end; uint32_t bitbuf; unsigned bitcount; unsigned curbits; uint16_t maxvalue; uint16_t dicreset; uint16_t incbits; uint16_t nextcode; uint16_t prev_code; uint16_t code; uint16_t cur; uint8_t first_char; sp = src; src_end = src + src_size; dp = dst; dst_end = dst + out_size; bitbuf = 0; bitcount = 0; curbits = 9; maxvalue = (uint16_t)((1u << curbits) - 1u); dicreset = (uint16_t)(maxvalue - 1u); incbits = (uint16_t)(maxvalue - 2u); nextcode = LZW_FIRST_CODE; #define GET_CODE(out_code) \ do { \ while (bitcount < curbits) { \ if (sp >= src_end) { \ return 2; \ } \ bitbuf = ((bitbuf << 8) | (uint32_t)(*sp++)) & 0xFFFFFFFFUL; \ bitcount += 8; \ } \ bitcount -= curbits; \ (out_code) = (uint16_t)((bitbuf >> bitcount) & maxvalue); \ } while (0) #define INC_BITS_IF_NEEDED() \ do { \ if (nextcode > incbits && curbits < LZW_MAX_BITS) { \ ++curbits; \ maxvalue = (uint16_t)((1u << curbits) - 1u); \ dicreset = (uint16_t)(maxvalue - 1u); \ incbits = (uint16_t)(maxvalue - 2u); \ } \ } while (0) if (out_size == 0) { return 0; } GET_CODE(code); if (code > 0xFFu) { return 3; } *dp++ = (uint8_t)code; prev_code = code; first_char = (uint8_t)code; for (;;) { unsigned sptr; if (dp >= dst_end) { return 0; } GET_CODE(code); if (code == maxvalue) { return 0; } if (code == dicreset) { nextcode = LZW_FIRST_CODE; GET_CODE(code); if (code == maxvalue) { return 0; } if (code > 0xFFu) { return 3; } *dp++ = (uint8_t)code; prev_code = code; first_char = (uint8_t)code; continue; } sptr = 0; cur = code; if (cur >= nextcode) { if (sptr >= LZW_STACK_CAP) { return 1; } stack[sptr++] = first_char; cur = prev_code; } while (cur > 0xFFu) { if (cur >= LZW_DICT_CAP || sptr >= LZW_STACK_CAP) { return 1; } stack[sptr++] = suffix[cur]; cur = prefix[cur]; } stack[sptr++] = (uint8_t)cur; first_char = (uint8_t)cur; while (sptr != 0) { if (dp >= dst_end) { return 4; } *dp++ = stack[--sptr]; } if (nextcode < LZW_DICT_CAP) { prefix[nextcode] = prev_code; suffix[nextcode] = first_char; } ++nextcode; INC_BITS_IF_NEEDED(); prev_code = code; } #undef GET_CODE #undef INC_BITS_IF_NEEDED } static int load_item_table(FILE *fp, uint32_t table_offset, uint16_t count, epfs_item **out_items) { epfs_item *items; uint16_t i; items = (epfs_item *)calloc((size_t)count, sizeof(items[0])); if (items == NULL) { return 0; } if (fseek(fp, (long)table_offset, SEEK_SET) != 0) { free(items); return 0; } for (i = 0; i < count; ++i) { uint8_t raw[EPFS_ITEM_SIZE]; if (!read_fully(fp, raw, sizeof(raw))) { free(items); return 0; } memcpy(items[i].name, raw, 13); items[i].name[13] = '\0'; items[i].packed = raw[13]; items[i].packed_size = rd32le(raw + 14); items[i].unpacked_size = rd32le(raw + 18); } *out_items = items; return 1; } static int extract_epf(const char *path) { FILE *fp; uint8_t hdr[EPFS_HEADER_SIZE]; uint32_t magic; uint32_t table_offset; uint8_t archive_flags; uint16_t count; epfs_item *items; uint16_t i; int rc; fp = fopen(path, "rb"); if (fp == NULL) { fprintf(stderr, "error: cannot open input file: %s\n", path); return 2; } rc = 3; items = NULL; if (!read_fully(fp, hdr, sizeof(hdr))) { fprintf(stderr, "error: cannot read EPF header\n"); goto done; } magic = rd32le(hdr + 0); table_offset = rd32le(hdr + 4); archive_flags = hdr[8]; count = rd16le(hdr + 9); (void)archive_flags; if (magic != EPFS_MAGIC) { fprintf(stderr, "error: invalid EPF signature\n"); goto done; } if (!load_item_table(fp, table_offset, count, &items)) { fprintf(stderr, "error: cannot read EPF file table\n"); goto done; } if (fseek(fp, EPFS_HEADER_SIZE, SEEK_SET) != 0) { fprintf(stderr, "error: cannot seek to data area\n"); goto done; } for (i = 0; i < count; ++i) { void *packed_buf; void *out_buf; size_t packed_size; size_t out_size; char out_name[14]; packed_size = (size_t)items[i].packed_size; out_size = (size_t)(items[i].packed ? items[i].unpacked_size : items[i].packed_size); memcpy(out_name, items[i].name, sizeof(out_name)); out_name[13] = '\0'; sanitize_name(out_name); printf("%s", out_name); packed_buf = malloc(packed_size ? packed_size : 1u); if (packed_buf == NULL) { printf(" - out of memory\n"); rc = 4; goto done; } if (packed_size && !read_fully(fp, packed_buf, packed_size)) { free(packed_buf); printf(" - short read\n"); rc = 5; goto done; } if (items[i].packed) { int urc; out_buf = malloc(out_size ? out_size : 1u); if (out_buf == NULL) { free(packed_buf); printf(" - out of memory\n"); rc = 4; goto done; } memset(out_buf, 0, out_size); urc = unlzw_epfs(items[i].unpacked_size, (const uint8_t *)packed_buf, items[i].packed_size, (uint8_t *)out_buf); free(packed_buf); if (urc != 0) { free(out_buf); printf(" - unpack error %d\n", urc); rc = 6; goto done; } } else { out_buf = packed_buf; } { FILE *fo = fopen(out_name, "wb"); if (fo == NULL) { free(out_buf); printf(" - cannot create output\n"); rc = 7; goto done; } if (out_size && !write_fully(fo, out_buf, out_size)) { fclose(fo); free(out_buf); printf(" - write error\n"); rc = 8; goto done; } fclose(fo); } free(out_buf); printf("\n"); } rc = 0; done: free(items); fclose(fp); return rc; } int main(int argc, char **argv) { printf("East Point Software .EPF unpacker v2.0\n"); printf("© CTPAX-X Team 2010,2018 © Lumis 2026\nhttp://www.CTPAX-X.org/\n\n"); if (argc != 2) { printf("usage: %s <file.epf>\n", argv[0]); return 1; } return extract_epf(argv[1]); } Спасибо сказали:
|
| -=CHE@TER=- |
Mar 9 2026, 18:39
Сообщение
#2
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Привет. По просьбе -=CHE@TER=- перегнал в ANSI C и протестировал на EPF от Aladdin 1996 с помощью chatgpt оригинальный код распаковщика ASM + C. В его тестовой среде в докере и у меня на винде отрабатывает чисто. Огромное-преогромное спасибо! Много лет с этим кодом маялся, и каждый раз когда пытался в нём разобраться голова трещала.Я также проверил на другой игре (ссылка), где, кстати, ещё и повреждённые файлы есть, и тоже всё правильно работает. Спасибо! Пара вопросов: 1. Можно ли оставить код работы с архивом старый (я там кое-чего обновил), а взять только ваш код распаковки LZW в отдельном файле "epfunlzw.c", который при компиляции автоматически будет включён в исходные коды через #include "epfunlzw.c"? Просто ИИ читает по частям, а мне хочется, чтобы размер и структура элементов архива сразу видны были при изучении исходных кодов. Ваши копирайты в шапку при запуске программы я добавлю обязательно. 2. Код для распаковки LZW тоже MIT? 3. Я поменял в функции распаковки LZW следующие вещи: - нормальный порядок параметров: packed_buffer, packed_size, unpacked_buffer, unpacked_size (когда это всё писалось много-много лет назад, я параметры в произвольном порядке объявлял); - вставил макрос INC_BITS_IF_NEEDED() в то место, где он используется, так как используется только один раз; - ничего больше трогать не стал, ибо боюсь поломать что-нибудь. Спасибо! Код для "epfunlzw.c" который планирую использовать: CODE /* This code by Lumis © Lumis 2026 Licensed under MIT */ #define LZW_FIRST_CODE 256 #define LZW_MAX_BITS 14 #define LZW_DICT_CAP 0x4680u /* from original buffers: 0x8D00 bytes / 2 */ #define LZW_STACK_CAP 0x0FA0u /* original hard stop */ /* * This is not textbook LZW. * * The reset code is (maxvalue - 1), end code is maxvalue. * The key quirk, matching the original game code: * reset clears nextcode back to 256, but DOES NOT reset bit width to 9. * * If you "fix" that into normal LZW, several real ALADDIN.EPF members fail * with bogus recursive chains and depth overflow. */ static int epfs_unlzw(const uint8_t *src, uint32_t src_size, uint8_t *dst, uint32_t out_size) { uint16_t prefix[LZW_DICT_CAP]; uint8_t suffix[LZW_DICT_CAP]; uint8_t stack[LZW_STACK_CAP]; const uint8_t *sp; const uint8_t *src_end; uint8_t *dp; uint8_t *dst_end; uint32_t bitbuf; unsigned bitcount; unsigned curbits; uint16_t maxvalue; uint16_t dicreset; uint16_t incbits; uint16_t nextcode; uint16_t prev_code; uint16_t code; uint16_t cur; uint8_t first_char; sp = src; src_end = src + src_size; dp = dst; dst_end = dst + out_size; bitbuf = 0; bitcount = 0; curbits = 9; maxvalue = (uint16_t)((1u << curbits) - 1u); dicreset = (uint16_t)(maxvalue - 1u); incbits = (uint16_t)(maxvalue - 2u); nextcode = LZW_FIRST_CODE; #define GET_CODE(out_code) \ do { \ while (bitcount < curbits) { \ if (sp >= src_end) { \ return 2; \ } \ bitbuf = ((bitbuf << 8) | (uint32_t)(*sp++)) & 0xFFFFFFFFUL; \ bitcount += 8; \ } \ bitcount -= curbits; \ (out_code) = (uint16_t)((bitbuf >> bitcount) & maxvalue); \ } while (0) if (out_size == 0) { return 0; } GET_CODE(code); if (code > 0xFFu) { return 3; } *dp++ = (uint8_t)code; prev_code = code; first_char = (uint8_t)code; for (;;) { unsigned sptr; if (dp >= dst_end) { return 0; } GET_CODE(code); if (code == maxvalue) { return 0; } if (code == dicreset) { nextcode = LZW_FIRST_CODE; GET_CODE(code); if (code == maxvalue) { return 0; } if (code > 0xFFu) { return 3; } *dp++ = (uint8_t)code; prev_code = code; first_char = (uint8_t)code; continue; } sptr = 0; cur = code; if (cur >= nextcode) { if (sptr >= LZW_STACK_CAP) { return 1; } stack[sptr++] = first_char; cur = prev_code; } while (cur > 0xFFu) { if (cur >= LZW_DICT_CAP || sptr >= LZW_STACK_CAP) { return 1; } stack[sptr++] = suffix[cur]; cur = prefix[cur]; } stack[sptr++] = (uint8_t)cur; first_char = (uint8_t)cur; while (sptr != 0) { if (dp >= dst_end) { return 4; } *dp++ = stack[--sptr]; } if (nextcode < LZW_DICT_CAP) { prefix[nextcode] = prev_code; suffix[nextcode] = first_char; } ++nextcode; if (nextcode > incbits && curbits < LZW_MAX_BITS) { ++curbits; maxvalue = (uint16_t)((1u << curbits) - 1u); dicreset = (uint16_t)(maxvalue - 1u); incbits = (uint16_t)(maxvalue - 2u); } prev_code = code; } #undef GET_CODE } Код главной программы "unepfs.c" если нужно (обновил всякого по мелочи): CODE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #pragma pack(push, 1) typedef struct { uint32_t magic; /* "EPFS" */ uint32_t toffs; /* TOC offset */ uint8_t flags; /* unused/unknown */ uint16_t count; /* files count */ } epfshead; typedef struct { char flname[13]; /* ASCIIZ filename */ uint8_t flflag; /* non-zero if LZW packed */ uint32_t pksize; /* packed size */ uint32_t unsize; /* unpacked size if LZW packed */ } epfsitem; #pragma pack(pop) #include "epfunlzw.c" static void dumpdata(char *name, void *data, uint32_t size) { FILE *f; if (name) { f = fopen(name, "wb"); if (f) { if (data && size) { fwrite(data, size, 1, f); } fclose(f); } } } static void readdata(FILE *fl, void *data, uint32_t size) { if (data && size) { memset(data, 0, size); if (fl) { fread(data, size, 1, fl); } } } int main(int argc, char *argv[]) { epfshead head; epfsitem *list; uint8_t *p, *u; uint32_t i; FILE *fl; printf( "East Point Software .EPF file unpacker v1.2\n" "© Lumis 2026\n" "© CTPAX-X Team 2010,2018\n" "http://www.CTPAX-X.org/\n\n" ); if (argc != 2) { printf("Usage: unepfs <filename.epf>\n\n"); return(1); } fl = fopen(argv[1], "rb"); if (!fl) { printf("Error: can't open input file.\n\n"); return(2); } /* read head */ readdata(fl, &head, sizeof(head)); if ((head.magic != 0x53465045) || (!head.count) || (head.toffs < sizeof(head))) { fclose(fl); printf("Error: invalid input file format.\n\n"); return(3); } /* allocate memory */ i = head.count * sizeof(list[0]); list = (epfsitem *) malloc(i); if (!list) { fclose(fl); printf("Error: not enough memory for contents table.\n\n"); return(4); } fseek(fl, head.toffs, SEEK_SET); readdata(fl, list, i); fseek(fl, sizeof(head), SEEK_SET); for (i = 0; i < head.count; i++) { printf("%s", list[i].flname); p = (uint8_t *) malloc(list[i].pksize); if (p) { readdata(fl, p, list[i].pksize); /* packed */ if (list[i].flflag) { u = (uint8_t *) malloc(list[i].unsize); if (u) { /* this for files like "4X44.MAP" from "Overdrive" which corrupted and stops unpack earlier than it should so we fill output buffer with zeros */ memset(u, 0, list[i].unsize); /* and this for files like "4X43.MAP" from "Overdrive" which corrupted and failed to unpack at all we must warn about it, but write what we can unpack anyway */ if (epfs_unlzw(p, list[i].pksize, u, list[i].unsize)) { printf(" - fatal error, code depth too great."); } list[i].pksize = list[i].unsize; free(p); p = u; } } /* dump file to disk */ dumpdata(list[i].flname, p, list[i].pksize); free(p); } else { /* skip file */ printf(" - not enough memory, skipping file."); fseek(fl, ftell(fl) + list[i].pksize, SEEK_SET); } printf("\n"); } free(list); fclose(fl); printf("\ndone\n\n"); return(0); } |
| Lumis |
Mar 9 2026, 19:13
Сообщение
#3
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Отлично, рад что работает четко! Я на него потратил минут 5 с поиском нужной EPF.
1. Конечно! 2. Мы трогали, рефакторили имеющийся код, разумно было бы сделать как в оригинале Apache 2.0, давайте его. Спасибо сказали:
|
| -=CHE@TER=- |
Mar 9 2026, 19:51
Сообщение
#4
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Огромное-преогромное спасибо!
Всё, сайт обновил! 15 лет эта штука на мозги капала, что нужно бы нормально сделать. Если будет ещё желание такими задачами заняться, то оставлю на будущее список всех программ со ссылками на страничку на сайте, где есть ассемблерный код: Battle of Heroes (Битва героев) .16B/.T16 extractor Вроде бы, тут какая-то разновидность RLE, но могу ошибаться. Galador (Ksiaze i Tchorz) .PTC unpacker Не помню что там, но, вроде бы, код не сильно сложный был. KGB (Conspiracy) .SQX unpacker Тут мало того что код под DOS, так он ещё и самоизменяющийся (!), что добавляет особенной боли. The Settlers IV .LIB unpacker Изначально код был с классами (ООП), поэтому я сильно замучился его выдирать и он тут может быть очень страшный, почти как для unepfs. Вроде бы, тоже какой-то LZW. Мне попадалась тема на StackOverflow (ссылка), где кто-то тщетно пытался этот код собрать, видимо, для x64, что, понятное дело, было невозможно. Turok 1/2 sound converter Сжатие звука. Кодек очень странный, много повторяющихся строк. Не думал, что помимо установки мелодии на телефон или простого любопытства узнать какие звуки есть в игре, кому-то эта программа пригодится исправлять косяки ремастера (ссылка). |
| PavelDAS |
Mar 25 2026, 07:11
Сообщение
#5
|
|
Advanced Member ![]() ![]() ![]() Группа: Authorized Сообщений: 31 Регистрация: 21-September 25 Из: Minsk Пользователь №: 18,037 Спасибо сказали: 8 раз(а) |
QUOTE Я также проверил на другой игре (ссылка), где, кстати, ещё и повреждённые файлы есть, и тоже всё правильно работает. Спасибо! Что именно с файлами не так? -------------------- |__--__|
***|*** ___|___ |
| -=CHE@TER=- |
Mar 25 2026, 15:23
Сообщение
#6
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Что именно с файлами не так? Там два файла в архиве "OVER.EPF" то ли криво упакованных из-за ошибки в реализации оригинального упаковщика, то ли повреждённых при записи на мастер-носитель, когда игру разработчики в печать отправляли:"4X43.MAP" - распаковка останавливается раньше времени с ошибкой "code depth too great", файл распаковывается только на 10%; "4X44.MAP" - распаковка останавливается раньше времени без ошибок, но внутри видно, что файл распакован только на 70%. Мы с Malvineous, автором проекта ModdingWiki (EPF Format), обсуждали по почте этот вопрос ещё в 2010 году и он сказал, что проверял эти файлы в игре (менял имя, чтобы эти трассы загружались первыми) и игра точно также падает с ошибками при попытке эти файлы использовать. Я тогда в Интернете поискал, везде была одна и та же копия игры. Возможно, с тех пор кто-то уже выложил исправную игру или сумел починить, но мне о таком неизвестно - буду рад, если кто-нибудь ссылку скинет. В Windows версии игры "Aladdin" файлы упакованы в .EPF архив (в отличие от DOS версии, где они просто в каталоге лежат). Так вот, там упаковывали, видимо, всё и сразу, поэтому в архив попали два лишних файла: ADD.BAT: CODE epfs -a aladdin.epf %1 copy /y aladdin.epf ..\exe MAKE.BAT: CODE del aladdin.epf ..\utils\epfs -a aladdin.epf *.* copy /y aladdin.epf ..\exe К сожалению, самого упаковщика "epfs" в архиве не было. Возможно, это помогло бы либо исправить файлы, либо отсечь версию с ошибкой в алгоритме сжатия. Я ещё пару игр смотрел, где были .EPF архивы, но ничего больше интересного не нашёл. P.S. Другое дело игра HAVOC, где разработчики умудрились исполняемый файл упаковщика в .FF архивы загнать, так что после распаковки можно сразу получить готовую утилиту для упаковки назад в комплекте. |
![]() ![]() |
| Упрощённая версия | Сейчас: 21st April 2026 - 08:39 |