Alien Trilogy (PC), разбор ресурсов |
Добро пожаловать, гость ( Вход | Регистрация )
Alien Trilogy (PC), разбор ресурсов |
LexSafonov |
Oct 29 2020, 17:48
Сообщение
#1
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Всем привет, решил создать тему про разбор форматов ресурсов игры Alien Trilogy, преимущественно её ПК варианта. Некоторые ресурсы лежат довольно открыто, но есть некоторые, весьма нестандартные.
Но обо всём по порядку. Начну я вот с чего. Из довольно открытых ресурсов - это звуки, это обычные raw-данные без заголовка. Спокойно съедается через Sound Forge или аналогичные программы, достаточно выставить нужный битрейт. Так же из открытых ресурсов можно выделить текстуры, о них я напишу ниже, т.к. это напрямую связано с "секциями" BND(B16) файлов. Кстати говоря о этих файлах. Игра использует BND "архивы", для хранения ресурсов - моделей, текстур, спрайтов, текстурных сеток для моделей. Ещё есть аналог BND - B16, в которых лежат данные от 16 битных вариаций текстур\спрайтов. Вернёмся к так называемым "секциям". В файлах встречаются вот такие секции: F000(#1,2,3....) - F? - Frame, кадр, используется такая секция у спрайтов. Может внутри себя содержать несколько изображений. Об этом я напишу далее и расскажу об одной особенности. T000(#1,2,3....) - T? - Texture, текстура. Тут всё проще, у таких секций есть строгие параметры, как у обычных изображений, длина ширина, нет компресии(что самое главное), изображение содержит идексы цветов. Спокойно дёргается XWE редактором. Может быть несколько штук в одном месте. Вообще эта секция достаточно интересная, т.к. используется у моделей\карт, рядом с этой секцией обычно бывает лежит ещё секция с индексами полигонов. M000(#1,2,3....) - M? - Model, модель. Тут всё просто, эта секция отвечает за модели. Имет список квадов(не полигонов!)\вершин. Прикол с квадами очевидно связан с тем, что изначально игру лепили для двух приставок - PS и Sega Saturn. Так вот, на сколько помню у сеги вроде бы аппаратная часть лучше работала именно с квадами. И вроде бы(поправьте, если не прав) - сначала игру делали именно для сеги. Но не суть, в файле именно описание квадов. В одном файле может быть несколько штук таких секций. BX00(#1,2,3....) - BX? - прямоугольники для текстурирования квадов(полигонов) в модели.Что то типа текстурной сетки. CX00(#1,2,3....) - CX? - пока не разбирал и не смотрел реакцию игры на эту секцию. Вот описание формата моделей текстом от моего товарища по думу ZZYZX: Разбор формата PICKMOD.BND текстом - сначала заголовок файла: размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x04 46 4F 52 4D FORM 0x04 - размер данных файла в байтах, BIG-ENDIAN (перевёрнутый как на сраном арме) 0x04 - количество моделей в файле текстом. всегда 4 символа (формат %04d) - дальше идут модели по очереди. у каждой модели есть: размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x01 4D первая буква идентификатора M (M000, M001, ...) 0x03 - индекс модели текстом. всегда три символа (формат %03d) 0x04 - размер данных модели в байтах, BIG-ENDIAN 0x04 4F 42 4A 31 OBJ1 0x08 00 00 00 00 00 00 00 00 неизвестное значение 0x04 - количество прямоугольников в модели. LITTLE-ENDIAN 0x04 - неизвестное значение 0x14*N - прямоугольники по очереди (см. формат дальше) 0x08*N - вершины по очереди (см. формат дальше) - формат прямоугольника: размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x04 - индекс первой точки. LITTLE-ENDIAN 0x04 - вторая точка 0x04 - третья точка 0x04 - четвёртая точка. может быть -1 (0xFFFFFFFF), тогда это треугольник и надо продублировать третью точку. 0x04 - неизвестное значение - формат вершины: размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x02 - координата X (signed, LITTLE-ENDIAN, short) 0x02 - координата Y 0x02 - координата Z 0x02 - неизвестное значение, вроде бы всегда 0 А вот и текстурная сетка для моделек Разбор формата PICKGFX.BND текстом - сначала заголовок файла: размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x04 46 4F 52 4D FORM 0x04 - размер данных файла в байтах, BIG-ENDIAN 0x04 50 53 58 54 PSXT (вероятно идентификатор формата) - дальше идут (в произвольном порядке?) секции INFO, TP00, CL00, BX00. Возможно бывают *01, *02 и так далее, но мне не встречались. - секция INFO размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x04 49 4E 46 4F INFO 0x04 - размер данных секции в байтах, BIG-ENDIAN 0x02 - размер текстуры X 0x02 - размер текстуры Y 0x0C - неизвестная информация, 12 байт - секция TP00 тут тупо лежат WxH пиксели. каждый пиксель = 1 байт. смещение в палитру текущую экрана. найти можно в PALS/WSELECT.PAL (768 байт, 3 байта на каждый цвет, умножить на 4 каждый компонент) - секция CL00 тут лежит неизвестно что. не кантовать. - секция BX00 тут лежат прямоугольники текстуры для текстурирования квадов. размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x04 42 58 30 30 BX00 0x04 - размер данных секции в байтах, BIG-ENDIAN 0x04 - количество прямоугольников - - прямоугольники по очереди (см. формат дальше) - формат прямоугольника текстуры BX00 размер содержимое комментарий ------------------------------------------------------------------------------------------------------------------------- 0x01 - размер по X (-1 пиксель, т.е. 31 вместо 32 и так далее) 0x01 - размер по Y (-1 пиксель) 0x01 - неизвестное значение 0x01 - неизвестное значение 0x01 - смещение по X с конца (т.е. надо отнять ширину перед использованием) 0x01 - смещение по Y C000(#1,2,3....) - C? - Color, цвет, секция, очевидно отвечающая за цвет. У файлов B16, такая секция идёт в самом конце. Формат этой секции такой: 0x04 - С000 0x04 - кол-во байт, отведённых под цвета\палитру. 0x02 - непосредственно цвета, по 2 байта на цвет(обычно) У этой секции есть одна особенность - в некоторых файлах бывает так, что кол-во байт под палитру меньше 512, не понял с чем это связано. Если я правильно понимаю логику, то 512\2 = 256 ячеек(если отталкиваться от каких то простых форматов тип BMP). Вроде бы во многих мануалах пишут, что 16 бит именно так и работает, поправьте, если я не прав. Теперь вернёмся с секциям F000, которые отвечают за спрайты. Собрал небольшое описание, в основном из экспериментов с пожатыми данными в Hex-редакторе. Временное описание формата: 0x04 - Заголовок файла FORM 0x04 - размер данных файла в байтах, BIG-ENDIAN 0x04 - кол-во блоков, видимо текст ------------------------------------------------------------------------------------------------------------------------- 0x04 - индентификатор F000 0x04 - Длина до следующей секции, видимо в бинарном представлении. 0x01 - Непонятный байт, крошит изображение, есть подозрение, что это длина алфавита или какой то цепочки байт. Это не длина\ширина. Ниже объясню. 0x0? - Цвета и повторения. Формат повторений 0x01 - Код цвета 0х01 - Флаг\префикс повторений(обычно символ P, т.е. в Hex коде 50) 0х01 - промежуток повторений(через какой промежуток надо повторить этот цвет) 0х01 - кол-во раз повторений(сколько пикселей рисовать в ряд https://www.old-games.ru/forum/attachments/...mat-png.211586/ немного наглядности из примерного описания формата, файл MM9.B16 По поводу формата повторений. У него тоже есть определённые условия, а именно, описание верно только для одного условия - когда первое число после префикса равно 1. Видимо для игры это обозначает рисовать цвет сплошняком. Плюс изначально для сплошного цвета игра прибавляет в ряд толи 8, толи 10 неубираемых пикселей. Я насчитал 10. В файле присутствуют варианты, когда первый байт после префикса больше единицы. Из своих экспериментов пока только сделал вывод, что что-то двигается(из пикселей), но не понял закономерности. Иногда бывает, что в повторениях походу висят какие-то ссылки на другие места. Кстати об этом тоже по подробнее. В один момент я решил разбирать вообще сплошняком побайтово, но запутался ещё больше: https://www.old-games.ru/forum/attachments/...ble-png.215950/ красный - видимо ссылки, при изменении ломают изображение желтый - меняют цвет в нескольких местах, либо подставляют туда какой то кусок синий - одиночный пиксель голубой - нет эффекта, либо эффект незаметен бардовый - нули, непонятно чё делают, если изменить на значение, отличное от нуля, то в изображении пропадают пиксели(местами). Из переписки с -=CHE@TER=- я увидел, что стандартных данных, как у любой другой картинки, в файле нет, т.к. он нашёл указатель на этот самый пистолет в TRILOGY.EXE, где благополучно лежит длина и ширина кадра. Теперь по поводу магического числа, ломающего изображение. В MM9.B16 это 92(Hex вариант). Я пошёл ещё дальше и решил сделать "чистый" кадр. Забил всё одиночными пикселями, с цветом 01, а само число 92 поставил на нуль. Получил такую картину: https://www.old-games.ru/forum/attachments/nulls-png.216418/ Откуда то взялась лесенка. Байт, где число 92, каким то образом на неё влияет, как будто сдвигает её. Закономерности сдвигов не понял, двигает всегда на разную длину(если менять значение). Потом забил серый цвет. Лесенка уменьшилась, а само изображение увеличилось(по идее ничего не должно было подобного произойти): https://www.old-games.ru/forum/attachments/probe-png.216419/ Вопрос к знатокам - может, кто то видел подобное? Похоже на LZX, но какое то своё, хитро-мудрое. Я описывал B16 вариант, но на сайте old-games, чувак с ником ak48 видимо пробовал смотреть BND аналог и там какие то вывороты с палитрой жёсткие) |
-=CHE@TER=- |
Oct 29 2020, 19:21
Сообщение
#2
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Ну, я уже писал, что смотрел игру бегло, там реально какое-то замороченное сжатие. Причём, на мой взгляд, его явно перемудрили.
Кстати, по поводу форматов файлов с ресурсами, всё забываю сказать - они используют Interchange File Format (IFF) как базу, но со своими блоками (F###, например). Прилагаю небольшую программу на сях выводящую блоки файла на экран (указывается как параметр командной строки). CODE #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <malloc.h> #define SWAP32(x) ((((x)>>24)&0xFF) | (((x)<<8)&0xFF0000) | (((x)>>8)&0xFF00) | (((x)<<24)&0xFF000000)) #pragma pack(push, 1) typedef struct { uint32_t mark; uint32_t size; } frmchunk; #pragma pack(pop) int main(int argc, char *argv[]) { frmchunk data; char s[5]; FILE *fl; if (argc != 2) { return(1); } fl = fopen(argv[1], "rb"); if (!fl) { return(2); } s[4] = 0; memset(&data, 0, sizeof(data)); fread(&data, sizeof(data), 1, fl); data.size = SWAP32(data.size); memcpy(s, &data.mark, 4); printf("%08lX: %4s %08X\n", ftell(fl) - sizeof(data), s, data.size); /* "MARK" */ if (data.mark == 0x4D524F46) { fread(s, 4, 1, fl); printf("Count: %4s\n", s); while (!feof(fl)) { memset(&data, 0, sizeof(data)); fread(&data, sizeof(data), 1, fl); data.size = SWAP32(data.size); memcpy(s, &data.mark, 4); if (!data.mark) { break; } printf("%08lX: %4s %08X\n", ftell(fl) - sizeof(data), s, data.size); fseek(fl, data.size, SEEK_CUR); } } fclose(fl); return(0); } |
LexSafonov |
Oct 30 2020, 19:34
Сообщение
#3
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Так, продолжаю работу по просмотру сырого дизассемблированного кода и параллельно отладочного. Вообщем решил посмотреть, как в IDA вообще обзывается указатель от пистолета, который ты мне скинул. И обнаружил собственно вот это:
По началу при беглом просмотре этих строк я и не думал, что это относится к кадрам, но сейчас до меня походу допёрло от чего это всё добро - это видимо что то от "анимационных структур". Я когда то находил жирную процедуру, которая "собирала" эти данные. Там даже есть указатели на текст ошибок, вроде как с жалобой на память. Есть теперь повод побаловаться и понаблюдать, что каждый байт делает. Может что то найдётся по распаковке, ведь там есть непонятные блоки-сегменты. Я подозреваю, что там могут быть ещё стандартные параметры, типа скорости кадра(сколько тиков он висит), какие то действия в кадре(выстрел\звук). Если мои догадки подтвердятся, то можно будет даже побаловаться со стандартными параметрами. Заодно решил бегло пробежаться по мануалу коробочного отладчика и нашёл способ, как остановить выполнение программы на этапе обращения к файлам через комманду BPINT 21 42. Сразу оговорюсь, что я нифига не понял про прерывания, но способ полезный, тормозит игру как раз, когда нужно. Сверялся с кодом из IDA Pro, пока нашёл одну процедуру в отладчике, которую не смог отыскать в дизассемблере. Наверное связано с тем, что я начал отлаживать только с третьего захода, когда открыло уже три файла. Пока отключаюсь, завтра ещё один заход сделаю, может что то интересное найду. |
-=CHE@TER=- |
Oct 30 2020, 20:24
Сообщение
#4
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
По началу при беглом просмотре этих строк я и не думал, что это относится к кадрам, но сейчас до меня походу допёрло от чего это всё добро - это видимо что то от "анимационных структур". Я когда то находил жирную процедуру, которая "собирала" эти данные. Там даже есть указатели на текст ошибок, вроде как с жалобой на память. Да, возможно. См. моё письмо - там с этого смещения и ниже (я писал в письме смещение) всё к пистолету относится. Там несколько структур.Заодно решил бегло пробежаться по мануалу коробочного отладчика и нашёл способ, как остановить выполнение программы на этапе обращения к файлам через комманду BPINT 21 42. Сразу оговорюсь, что я нифига не понял про прерывания, но способ полезный, тормозит игру как раз, когда нужно. В DOS всё делается через вызовы прерываний.BPINT = Break Point INTerrupt - т.е. поставить бряк на вызов прерывания. Прерывание 21 - это сервисы операционной системы DOS. 42 - номер функции (складывается в регистр ah). Т.е. бряк сработает на таком коде: CODE ; здесь могут задаваться параметры mov ah, 42h ; здесь могут задаваться ещё параметры int 21h; <--- вот тут будет останов, т.к. ah = 42h См. справку TechHelp!, про которую я в теме про отладчик писал. Там в меню выбираешь: TECH Topics -> DOS Functions QuickRef И ищешь там 42 - это "42H Move File Ptr". Иными словами, ты поставил бряк на фунцию seek(). BPINT 21 3D - открытие файла, если поставил такой бряк, то когда он сработал нажми Alt+X - увидишь сверху в дампе памяти имя открываемого файла. BPINT 21 3F - чтение файла, опять же Alt+X - буфер, куда будет записан результат после выполнения прерывания. Ну и так далее - смотри справку по этим функциям в TechHelp! Только помни о том, что TechHelp! написан для реального режима, поэтому у тебя в защищённом будет не bx, а ebx, не di, а edi и так далее. Остаются как были только сегментные регистры es, ds, cs, ss. Сверялся с кодом из IDA Pro, пока нашёл одну процедуру в отладчике, которую не смог отыскать в дизассемблере. Ты куда-то ушёл не туда - это уже область данных. В частности, ты стоишь на строке "0123456789abcd", которую дизассемблер пытается как-то дизассемблировать.Наверное связано с тем, что я начал отлаживать только с третьего захода, когда открыло уже три файла. Пока отключаюсь, завтра ещё один заход сделаю, может что то интересное найду. |
LexSafonov |
Oct 31 2020, 09:46
Сообщение
#5
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Так, сегодня сел со вежей головой и решил прикинуть устройство анимаций оружий. Вообщем набросал я вот что:
Из своего опыта по конверсиям я прикинул вот такое описание. У оружий есть состояния, а именно: Idle - простой\бездействие Fire - огонь AltFire - альтернативный огонь, нашёл только у пульсатора. Выстрел подствольником. Reload - "перезарядка" Забавный факт, у огнемёта тоже видимо есть состояние перезарядки, но видимо игра это поле тупо игногрит. Наверное это когда то начали делать и потом забили, а вычищать банально не стали, т.к. "никто же не полезет туда")))) У дробовика только 2 состояния - простой и выстрел, т.к. спрайты перезарядки дробовика рисуются сразу после выстрела. У пульсатора самое большое кол-во состояний из-за альтернативной стрельбы подствольником. У кадого состояния есть 2 поля - в одном(без приписки Pic) описывается размер картинки и какие то другие параметры(вполне вероятно может быть офсет на самом экране, но это не точно). Может содержать данные на несколько картинок, по очереди. Во втором видимо что-то по скоростям и действиям(вчера ночью я умудрился сделать так, что спрайт пистолета вообще самоуничтожился через какое то время в белый прямоугольник и игра вылетела). Сразу сделаю оговорку, в полях с припиской Pic я пока не разобрался(я просто это обозвал так, чтобы не путаться). Сегодня на досуге ещё по монстрам пробегусь, я так думаю там есть состояния не только простоя, но и различных атак, и даже наверное что то по "углам" относительно взгляда игрока(какие то состояния для рендера). |
-=CHE@TER=- |
Oct 31 2020, 17:22
Сообщение
#6
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Всё, я нашёл функцию декодирования.
Ищешь в исполняемом файле следующую последовательность байт: 89 55 FC D1 6D FC F6 45 FD FF Это как раз самое начало этой функции. Причёсанный код на сях ниже. Должен работать как и оригинал, если я ничего не напутал там. Первый параметр (p) - указатель на сжатые данные, второй (u) - на буфер куда распаковывать. Кстати, рядом с этой функцией (то ли после, то ли до) есть ещё одна, которая тоже что-то распаковывает, но там немного другой алгоритм. В месте где вызывается эта функция стоит проверка, что если какой-то параметр не ноль, то будет вызвана эта функция, иначе - вторая. CODE void PicDecoder(uint8_t *p, uint8_t *u) { int32_t i, offs, size; i = 0; while (1) { while (1) { i >>= 1; if (!(i & 0xFF00)) { i = 0xFF00 | *p; p++; } if (i & 1) { break; } *u = *p; u++; p++; } if (*p >= 96) { offs = *p - 256; size = 3; p++; } else { size = (*p & 0xF0) >> 4; offs = (*p & 0x0F) << 8; p++; offs |= *p; p++; if (!offs) { break; } offs = -offs; if (size == 5) { size = *p + 9; p++; } else { size = size + 4; } } while (--size) { *u = u[offs]; u++; } } } |
LexSafonov |
Nov 1 2020, 08:11
Сообщение
#7
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Блин, бро, да ты мозг! Прям интересно стало, что там вывертеть могли...
Проверка возможно связана с тем, что в BND вариантах лежат "зернистые" варианты спрайтов (режим 256 цветов). Так, сейчас пойдут глупые вопросы) Никак не могу в голове прикинуть этот код к тем данным, которые в файле. Ну вот вообще. Я только понял, что часть каких то пикселей строится из предыдущих Что делает второй цикл в алгоритме? CODE while (1) { i >>= 1; //Не понял для чего сдвиг вправо, там же по идее в переменной нуль. if (!(i & 0xFF00)) { //Не догоняю, что обозначает 0xFF00 и что там пытаются проверить. i = (0xFF00) | *p; //Тоже не понял для чего оно. p++; } if (i & 1) { break; } *u = *p; u++; p++; } Чёт у алгоритма математика какая то вывернутая. Делать чтоль нефиг было разработчикам.... Столько переменных связано с тем, что надо помнить старые данные? Или я опять что то недопонял... Хочу простенькую прогу набросать просто. Сорян, что вопросы такие, не работал просто с побитовыми операторами толком) P.S. ааа, блин, понял, частично, у нас же не один заход цикл делает. Но тем неменее по первому циклу я не догоняю, что это. Это то пресловутое "скользящее окно"? |
-=CHE@TER=- |
Nov 1 2020, 18:03
Сообщение
#8
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Проверка возможно связана с тем, что в BND вариантах лежат "зернистые" варианты спрайтов (режим 256 цветов). Вряд ли, алгоритм сжатия там, скоре всего, один. Второе сжатие, скорее всего, для другого типа данных.// Не понял для чего сдвиг вправо, там же по идее в переменной нуль. Для первого прохода не важно. Это для следующих.// Не догоняю, что обозначает 0xFF00 и что там пытаются проверить. Что старший байт ноль.i & 0xFF00 - будет равно 0x##00, где ## - старший байт в слове. Далее: //Тоже не понял для чего оно. Если старший байт в слове ноль, то он заменяется на 0xFF.Например, так: 0x0011 => 0xFF11 0x0034 => 0xFF34 Это то пресловутое "скользящее окно"? Скользящее окно - это нижний цикл "while (--size)". А здесь копируется байт из входного потока в выходной:CODE while (1) { // бесконечный цикл // сдвигаем переменную на один бит вправо i >>= 1; // если старший байт ноль if (!(i & 0xFF00)) { // то заменяем на 0xFF i = (0xFF00) | *p; // и переходим к следующему байту во входном потоке p++; } // если младший бит зажжён - выходим из этого цикла if (i & 1) { break; } // если нет, то копируем байт входного потока в выходной *u = *p; // и переходим к следующим байтам в обоих потоках u++; p++; } Вообще, если ты в студии или другой IDE программу на сях компилируешь, то можешь сделать отладку по шагам, вывести значения на каждом шаге, например, в консоль, и смотреть что происходит. Короче, ладно, так уж и быть, причесал код - обновил своё предыдущее сообщение, см. код там. Отсюда, кстати, хороши видно, что это слегка расширенный LZX наоборот. Если в LZX бит 1, то копируется входной байт из потока в выходной. А тут иначе - если ноль, то копируется байт (вложенный цикл с 0xFF00), а если 1, то плавающее окно (выход из цикла и далее по коду). Сравни с кодом из темы по программированию на которую я тебе ссылку в письме давал. |
LexSafonov |
Nov 11 2020, 08:21
Сообщение
#9
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Так, ещё порция глупых вопросов, по поводу того алгоритма декомпресии. В функцию надо передавать указатель на файл? Или же надо файл считать в массив элементов и потом в качестве параметра указать его?(Имеется ввиду *p) Аналогично для второго параметра, который отвечает за данные на выходе.
И ещё столкнулся с одной проблемой - как в сишке тип uint8_t подключить? Есть ли заголовочные файлы в самой студии для таких типажей? Начал с простого, сделал простенький "раскомпоновщик" BND файлов на отдельные секции. Думаю через код декомпресии прогонять эти раскомпонованые файлы. Не знаю, правильно или нет, делаю как можно проще, из-за малого опыта. |
-=CHE@TER=- |
Nov 11 2020, 12:59
Сообщение
#10
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
В функцию надо передавать указатель на файл? Или же надо файл считать в массив элементов и потом в качестве параметра указать его?(Имеется ввиду *p) Аналогично для второго параметра, который отвечает за данные на выходе. Так я же писал, что это буфер (оба параметра):Первый параметр (p) - указатель на сжатые данные, второй (u) - на буфер куда распаковывать. И ещё столкнулся с одной проблемой - как в сишке тип uint8_t подключить? Есть ли заголовочные файлы в самой студии для таких типажей? Это нужно подключить заголовчный файл stdint.h - если компилятор не самый древний, то этот файл должен быть (у меня в GCC за 2002 год уже есть).Вот код программы, которая целиком извлекает все кадры. Работает также и с .BND файлами, но там палитра будет левая (я её строю так, чтобы цвета не совпадали, но при этом выглядит оно так себе). Чёрный цвет (нулевой, прозрачный) я закрашиваю фиолетовым, чтобы фон было отчётливо видно. Т.к. у рисунков нет размеров, то их нужно указывать из командной строки (у MM9 все три кадра с разным размером, как оказалось). При запуске без параметров или недостающим их количеством будет выведена справка с примером работы. Если размер рисунка неизвестен, то нужно поставить достаточно большие размеры, затем меняшь ширину, пока рисунок не станет понятным, затем уменьшать высоту, пока снизу не исчезнет заливка из байт 0xFF (255) - какой это цвет будет зависит от палитры (в MM9.B16 это белый). Да, у кадров снизу могут быть прозрачные (фиолетовые) строки пикселей после изображения - это нормально. Если уменьшить высоту рисунка, чтобы их не было, то программа будет падать! Обращаю внимание на то, что если размер рисунка слишком маленький, то программа упадёт с фатальной ошибкой! Потому что функция декодирования изображения поразумевает что вызывающий её код знает размер кадра и выделил соответствующий буфер под это дело. В нормальном коде в функцию декодирования обычно передаются не только указатели на входной и выходной буфер, но и их размеры - как раз, чтобы подобные вещи предотвратить. Но, как я уже сказал, этот код выдернут и слегка причёсан из игры и у меня нет желания его менять (оставлю в качестве домашней работы всем желающим). Ах да, .TGA файлы с 16-ти битной палитрой могут неправильно показывать некоторые программы. Из того что было под рукой ACDSee 5 (2002) и Photoshop 7 (2002) справились. CODE /* atimgext.c */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <malloc.h> #define SWAP32(x) ((((x)>>24)&0xFF) | (((x)<<8)&0xFF0000) | (((x)>>8)&0xFF00) | (((x)<<24)&0xFF000000)) #pragma pack(push, 1) typedef struct { uint32_t mark; uint32_t size; } frmchunk; typedef struct { uint8_t IDLength; uint8_t ColorMapType; uint8_t ImageType; uint16_t CMapStart; uint16_t CMapLength; uint8_t CMapDepth; uint16_t XOffset; uint16_t YOffset; uint16_t Width; uint16_t Height; uint8_t PixelDepth; uint8_t ImageDescriptor; } tga_head; #pragma pack(pop) void PicDecoder(uint8_t *p, uint8_t *u) { int32_t i, offs, size; i = 0; while (1) { while (1) { i >>= 1; if (!(i & 0xFF00)) { i = 0xFF00 | *p; p++; } if (i & 1) { break; } *u = *p; u++; p++; } if (*p >= 96) { offs = *p - 256; size = 3; p++; } else { size = (*p & 0xF0) >> 4; offs = (*p & 0x0F) << 8; p++; offs |= *p; p++; if (!offs) { break; } offs = -offs; if (size == 5) { size = *p + 9; p++; } else { size = size + 4; } } while (--size) { *u = u[offs]; u++; } } } int main(int argc, char *argv[]) { FILE *fl, *f; frmchunk data; tga_head th; char s[5], name[16]; uint16_t pal[256]; uint32_t i, n, l, sz; uint8_t *p, *u; printf( "Alien Trilogy .B16 to .TGA image converter v1.0\n(" "c) CTPAX-X Team 2020\nhttp://www.CTPAX-X.org/\n\n" ); if (argc < 4) { printf( "Usage: atimgext <filename.b16> <frame0 width> <frame0 height> [...]\n\n" "Example:\natimgext MM9.B16 40 68 40 88 76 84\n\n" ".BND images supported, but without proper palette since it is stored elsewhere.\n" "If you don't know exactly frames width or height make them big enough so the\n" "bottom rows on the image will be filled with 255 (0xFF) bytes. From there you\n" "can change width and reduce height to find actual frame dimensions.\n\n" ); return(1); } /* init tga header */ memset(&th, 0, sizeof(th)); th.ImageDescriptor = 0x20; th.ColorMapType = 1; th.ImageType = 1; th.CMapLength = 256; th.CMapDepth = 16; /* 24 */ th.PixelDepth = 8; fl = fopen(argv[1], "rb"); if (!fl) { printf("Error: can't open input file.\n\n"); return(2); } /* read header */ memset(&data, 0, sizeof(data)); fread(&data, sizeof(data), 1, fl); sz = SWAP32(data.size); /* "FORM" */ if ((data.mark != 0x4D524F46) || (!sz)) { fclose(fl); printf("Error: invalid input file format.\n\n"); return(3); } s[4] = 0; fread(s, 4, 1, fl); n = atoi(s); printf("Frames: %u\n\n", n); if (argc < (2 + (n * 2))) { fclose(fl); printf( "Error: %d arguments are given, but %d required:\n" "(1 input filename + %d pairs of width and height for each frame).\n\n", argc - 1, 1 + (n * 2), n ); return(4); } /* create some default palette in case of .BND (Bitmap iNdexeD color images) */ for (l = 0; l < 256; l++) { pal[l] = 0x8000 | ((l & 0xF0) << 6) | ((l & 0x0F) << 5) | 0x001F; } /* transparent to purple */ pal[0] = 0xFC1F; sz += 8; i = 0; while (ftell(fl) < sz) { memset(&data, 0, sizeof(data)); fread(&data, sizeof(data), 1, fl); data.size = SWAP32(data.size); memcpy(s, &data.mark, 4); if (!data.mark) { break; } printf("%08lX: %4s %08X\n", ftell(fl) - sizeof(data), s, data.size); /* Frame */ if ((data.mark & 0xFF) == 'F') { th.Width = atoi(argv[2 + (i * 2)]); th.Height = atoi(argv[2 + (i * 2) + 1]); l = th.Width * th.Height; p = (uint8_t *) malloc(data.size); u = (uint8_t *) malloc(l); if (p && u) { memset(u, 0xFF, l); fread(p, data.size, 1, fl); PicDecoder(p, u); sprintf(name, "FRAME%02u.TGA", i); f = fopen(name, "wb"); if (f) { fwrite(&th, sizeof(th), 1, f); fwrite(&pal, sizeof(pal), 1, f); fwrite(u, l, 1, f); fclose(f); } } if (u) { free(u); } if (p) { free(p); } i++; continue; } /* Colormap */ if ((data.mark & 0xFF) == 'C') { /* in case of less than 256 colors in Colormap */ l = sizeof(pal); memset(pal, 0, l); l = (l < data.size) ? l : data.size; fread(pal, l, 1, fl); if (l < data.size) { fseek(fl, data.size - l, SEEK_CUR); } /* fix palette */ for (l = 0; l < 256; l++) { /* T R G B F39C -> 1 11100 11100 11100 */ pal[l] = (pal[l] & 0x8000) | ((pal[l] & 0x7C00) >> 10) | (pal[l] & 0x03E0) | ((pal[l] & 0x001F) << 10); } /* transparent to purple */ pal[0] = 0xFC1F; continue; } /* unknown block - skip */ fseek(fl, data.size, SEEK_CUR); } fclose(fl); /* replace palette in already extracted frames */ for (i = 0; i < n; i++) { sprintf(name, "FRAME%02u.TGA", i); f = fopen(name, "r+b"); if (f) { fseek(fl, sizeof(th), SEEK_SET); fwrite(&pal, sizeof(pal), 1, f); fclose(f); } } printf("\ndone\n\n"); return(0); } |
LexSafonov |
Nov 11 2020, 14:09
Сообщение
#11
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Ух ты. Даже как то не по себе, от того, что ты прогу написал
Я вообще думал как сделать - отдельная прога "раскомпановщик", потом отдельная прога "декомпрессор" и уже последняя для просмотра "сырых" декомпрессованых данных, где можно было бы на ходу забивать длину, ширину и смещение от начала файла. Там вроде есть секции, в которых содержится сразу несколько спрайтов и, бывает, разных размеров. У BND там палитра какая то стрёмная, чувак с ником ak47 описывал свои наблюдения, как она работает. С его слов я понял только, что один файл с палитрой имеет в себе несколько "наборов" на файлы со спрайтами, строго фиксированные. Я это не смотрел, т.к. всё время пытался разобрать формат B16 и цвета я сразу в первое время нашёл(от того и надобности не было в поиске палитры). Ты уж извини за глупые вопросы. В качестве "домашней работы" я таки попробую по своему методу, простыми линейными прогами, а потом уже, если получится, попробую что-то по серьёзнее |
-=CHE@TER=- |
Nov 11 2020, 18:02
Сообщение
#12
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Там вроде есть секции, в которых содержится сразу несколько спрайтов и, бывает, разных размеров. Ну, вот, тебе будет что допиливать.Я это не смотрел, т.к. всё время пытался разобрать формат B16 и цвета я сразу в первое время нашёл(от того и надобности не было в поиске палитры). Кстати, как я и говорил - сжатие у .B16 и .BND одинаковое.Ты уж извини за глупые вопросы. Нормальные вопросы. Что ты всё время извиняешься-то.В качестве "домашней работы" я таки попробую по своему методу, простыми линейными прогами, а потом уже, если получится, попробую что-то по серьёзнее Ради бога - как тебе будет удобнее. |
LexSafonov |
Nov 13 2020, 16:01
Сообщение
#13
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Так, сделал простую прогу, линейную, для декомпресии. Но столкнулся вот с такой картиной:
Пришлось чутка переделать функцию, ну и параллельно изучить, как она работает с данными. Я сделал собственно вот так: CODE int main(int argc, char *argv[]) { char *FileName = argv[1]; FILE* InputFile; //Входной файл FILE* OutputFile;//Выходной файл InputFile = fopen(FileName, "rb"); OutputFile = fopen("Decompress", "ab+"); int CurrentPosition = 0; fseek(InputFile, 0, SEEK_END); int LengthOfFile = ftell(InputFile); unsigned char *InputBuffer = new unsigned char[LengthOfFile]; int CurrentByte; while(!feof(InputFile)) { fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); InputBuffer[CurrentPosition] = CurrentByte; cout << InputBuffer[CurrentPosition]; CurrentPosition++; } int i, offs, size; i = 0; while (1) { //-----------------------------------------// // while (1) { //0 //*p = 92 //FF92 //AA? i >>= 1; // Сдвиг вправо //0 //7FC9 cout << int(*InputBuffer) << endl; cout << i << endl; system("pause"); // Только для тех, у кого MS Visual Studio if (!(i & 0xFF00)) // если старший байт ноль { i = 0xFF00 | *InputBuffer; // то заменяем на 0xFF //конкатенация с текущим байтом *p FF92 InputBuffer++; // и переходим к следующему байту во входном потоке // *p = 00 } if (i & 1) // если младший бит зажжён - выходим из этого цикла // i = FF92 { break; } //*u = *p; // если нет, то копируем байт входного потока в выходной *u = 00 // u++; // и переходим к следующим байтам в обоих потоках CurrentByte = *InputBuffer; fwrite(&CurrentByte, 1, 1, OutputFile);//yyy InputBuffer++; // 50 } //-----------------------------------------// //В начале цикл вываливается с символа P(50), после третьего захода(?). if (*InputBuffer >= 96) { offs = *InputBuffer - 256; size = 3; InputBuffer++; } else { //*p = 50 //-----------Это блок, отвечающий за повторы? size = (*InputBuffer & 0xF0) >> 4; //5 //Математика для символа P? offs = (*InputBuffer & 0x0F) << 8; //0 InputBuffer++; //*p = 01 offs |= *InputBuffer; //offs = 01 //cout << int(size) << endl; InputBuffer++; //27 if (!offs) { break; } //не правда? не нуль? offs = -offs; //-1? if (size == 5) { size = *InputBuffer + 9; //48 InputBuffer++; //AA } else { size = size + 4; } } while (--size) //Сколько раз повторить { CurrentByte = offs; fwrite(&CurrentByte, 1, 1, OutputFile); //cout << "Offs byte"; cout << offs << endl; //system("pause"); // Только для тех, у кого MS Visual Studio //*u = u[offs]; //u++; } } system("pause"); // Только для тех, у кого MS Visual Studio return 0; } Не соображу никак, почему такая картина происходит. На прогу TiledGGD не обращай внимания, просто под руку попалась(она позволяет сырые данные смотреть). Там есть комменты типа "/FF92 //AA?" - это я для себя писал, когда отслеживал, как прога с данными работает. А блин, понял, я же неправильно скользящее окно сделал... Там же напрямую пишет откуда данные брать... |
-=CHE@TER=- |
Nov 13 2020, 17:21
Сообщение
#14
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
А блин, понял, я же неправильно скользящее окно сделал... Там же напрямую пишет откуда данные брать... Ну, у тебя неправильно скользящее окно сделано. Когда смещение делается отрицательным (offs = -offs), то это означает что у тебя на текущую (последнюю) позицию в выходном буфере / файле пишется байт с позиции -offs символов назад. Вот это выражение, которое "*u = u[offs]".LZX же как работает - если у тебя встречается какая-то последовательность байт, которая уже есть где-то в начале и не сильно далеко (иначе не хватит байт для кодирования расстояния), то сохраняется ссылка назад на то место и какое количество байт повторяется (сколько скопировать оттуда) - за это как раз отвечают собранные из байт потока переменные offs (offset - смещение) и size (size - размер). По мелочи: if (!offs) { break; } //не правда? не нуль? В сях любое выражение не равное нулю - истина. Собственно, это условие можно записать как "if (offs == 0)", но "if (!offs)" просто короче и удобнее. Оператор "!" это логическое отрицание, т.е. если после него стоит ложь, то она превратится в истину и наоборот (как мы помним условный оператор if срабатывает, только если условие внутри истино). Собственно и вот это:if (!(i & 0xFF00)) Можно записать как: if ((i & 0xFF00) == 0) Или даже так (только если подключён заголовочный файл <windows.h>): if (HIBYTE(i) == 0) HIBYTE() - макрос, который берёт старший (high) байт в слове Пример: i = 0x1234; LOBYTE(i) == 0x34 HIBYTE(i) == 0x12 Т.к. нас здесь конкретное значение не интересует, то достаточно через операцию "битовое и" (&) проверить что старший байт пуст. Здесь же отмечу, что "while (1)" - это запись бесконечного цикла (ибо единица никогда нулю не будет равна - "while (condition true)" переводится как "пока (условие истино)"), поэтому внутри него всякие "break" и стоят, чтобы хоть где-то, по какому-то условию, выход был. system("pause"); // Только для тех, у кого MS Visual Studio Будет работать и в GCC и в других компиляторах, потому что system() - это библиотечная функция вызова команды из операционной системы. Например, попробуй написать:system("calc"); И посмотри что получится. |
LexSafonov |
Nov 13 2020, 17:51
Сообщение
#15
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
Получилось! Переделал немного, сделал выходной буффер и с него уже записывал в файл, чтобы не нарушать математику процесса
Можно наверное это опубликовать всё на олд-геймсе(исходные коды, проги), если ты не против. Кто захочет посмотреть, он и через TiledGGD посмотрит, а я сосредоточусь на простеньком аналоге этого самого вьювера сырых данных) |
-=CHE@TER=- |
Nov 13 2020, 20:54
Сообщение
#16
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Получилось! Во, молодец!Можно наверное это опубликовать всё на олд-геймсе(исходные коды, проги), если ты не против. Лучше просто ссылку на эту тему дать.Кто захочет посмотреть, он и через TiledGGD посмотрит, а я сосредоточусь на простеньком аналоге этого самого вьювера сырых данных) Если ты про свой код - публикуй где хочешь и как хочешь конечно. Это же ты его написал. |
LexSafonov |
Nov 13 2020, 22:15
Сообщение
#17
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
На счёт исходных кодов - и твои и мои. Если ты не против конечно.
Ссылку выложу. Я нашёл один нюанс, кажись понял как делятся секции на спрайты. Общий цикл вываливается на условии: if (!offs) { break; } Как я понял это условие срабатывает, когда встречается пару нулей подряд. Сравнивал с hex-кодом - и вправду. Надо добавить будет условие на повторный запуск цикла, если спрайтов больше одного(ну и поиграться с указателем). Либо, если не получится, то написать отдельный декомпановщик секций... Завтра подумаю над этим, сейчас уже голова чёт не варит. |
-=CHE@TER=- |
Nov 14 2020, 12:05
Сообщение
#18
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
|
LexSafonov |
Nov 16 2020, 19:24
Сообщение
#19
|
Member Группа: Authorized Сообщений: 21 Регистрация: 29-October 20 Пользователь №: 18,034 Спасибо сказали: 1 раз(а) |
И так, после долгого мучения и раздумий, я наконец заставил мои 2 инструмента работать в полной мере, Пока что очень тестово(т.к. наверняка есть баги, которые я не успел отловить).
BNDEXTRACTOR - Это инструмент "декомпановщик" BND(B16) бинарных файлов. Использование: Перетащите BND(B16) файл на exe файл программы. В корневой директории программы создадутся файлы с такими именами - F000/1/2/3 и C000. Секции F000\1\2\3 - секции, содержащие в себе спрайты. В одной и той же секции могут быть по несколько штук спрайтов. Данные секции - это "сырые" сжатые спрайты, информация о которых зашита в исполняемый файл игры. Секция C000 - есть только у B16 вариантов файлов со спрайтами, это секция - 16 бит палитра. Содержит в себе по 2 байта на цвет. Недоработка: все файлы на выходе дозаписываются в конец, перед повторным использованием убирайте старые декомпанованые файлы. После того, как вы "декомпануете" BND(B16) файл, используйте далее инструмент FDecompressor. FDecompressor - это инструмент, который одновременно "декомпанует" секции F000\1\2\3 на отдельные одиночные спрайты и декомпрессует их. Использование: Перетащите файл с названием F000\1\2\3 на исполняемый файл. На выходе получаются файлы D000\1\2\3...N, которые можно спокойно просмотреть инструментами для "сырых" данных. Например TiledGGD. В TiledGGD в качестве палитры выбирайте файл с названием C000 и выставляйте режим 2 байта на цвет. Недоработка: все файлы на выходе дозаписываются в конец, перед повторным использованием убирайте старые декомпанованые файлы. Ссылка на архив с инструментами: http://www.filedropper.com/trilogydecompress Ну и пару скриншотов на последок, для затравки)) Обе программы чисто исходными кодами(если вдруг файл снесут с хостинга, можно будет скомпилить в 2008 студии) Декомпановщик CODE // BNDEXTRACTOR - программа "раскомпоновщик" структур BND-архивов. По сути это даже не архивы, а контейнеры со своими блоками(секциями). //Сразу делаю оговорку на то, что у игры есть два варианта спрайтов - B16 и BND. Файлы B16 хранят в себе дополнительную секцию с цветами C000 и более насыщены. //Bnd варианты имеют слишком "зернистую" графику из-за строгой привязанности к простой "общей" палитре(эдакий облегчённый вариант). //На данный момент пишу по простому. Из-за недостатка знаний. #include "stdafx.h" #include <math.h> #include <iostream> #include <fstream> #include <string> #include <stdlib.h> #include <stdint.h> #include <cstdlib> // для system using namespace std; typedef unsigned __int32 uint32_t; int main(int argc, char *argv[]) { char *FileName = argv[1]; //Текущее имя файла. Нюанс - в параметрах коммандной строки массив строк, строку надо перевести в массив символов. char ch; char FileExt[4]; FileExt[3] = '\0'; int ProgrammMode; //Чтобы "извлечь" секцию цветов, если таковая есть. А она есть в B16 файлах. int i = 0; while(true) { ch = FileName[i]; if(ch != 0) { if(ch == '.') { i++; FileExt[0] = FileName[i]; i++; FileExt[1] = FileName[i]; i++; FileExt[2] = FileName[i]; if(FileExt[0] == 'B' && FileExt[1] == 'N' && FileExt[2] == 'D') { ProgrammMode = 0; cout << "Set BND mode" << endl; break; } else if(FileExt[0] == 'B' && FileExt[1] == '1' && FileExt[2] == '6') { ProgrammMode = 1; cout << "Set B16 mode" << endl; break; } } } i++; } unsigned char ByteBuffer[4]; //Все смещения и длины в файле представлены в прямом порядке, а не в обратном, как читает сишный язык. Этот "буффер" решает проблему. int FileLenth; //Размер файла, 4 байта int SectionNumbers; //Это чтобы собрать текст в число в правильном порядке. char SectionName[5]; //Название секции, текст, 4 байта SectionName[4] = '\0'; //Это чтобы собиралось название, обрубает всё "нулём". int SectionLenth; //Длина секции, 4 байта int ColorNumbers; //Кол-во цветов в секции int CurrentPosition; //Указатель в файле FILE* BNDFileIn; //Входной файл FILE* BNDFileOut;//Выходной файл BNDFileIn = fopen(FileName, "rb"); //-------Существуют ли наши файлы-----// if(BNDFileIn == NULL) // Существует ли такой файл в папке с программой. { cout << "Can't open input file" << endl; system("pause"); return 0; } CurrentPosition=4; fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&ByteBuffer, 1, 4, BNDFileIn); FileLenth = ByteBuffer[3] | (ByteBuffer[2] << 8) | (ByteBuffer[1] << 16) | (ByteBuffer[0] << 24); //Разворачиваем число и получаем корректную длину файла. CurrentPosition+=4; //Ещё на 4 байта вперёд, читаем кол-во секций. fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&ByteBuffer, 1, 4, BNDFileIn); ByteBuffer[0] -= 0x30;//Делаем из текста число. ByteBuffer[1] -= 0x30; ByteBuffer[2] -= 0x30; ByteBuffer[3] -= 0x30; SectionNumbers = ByteBuffer[3] | (ByteBuffer[2] << 8) | (ByteBuffer[1] << 16) | (ByteBuffer[0] << 24); //Тоже самое, только с кол-вом секций. CurrentPosition+=4; //Ещё на 4 байта вперёд, читаем название секции, которое запишем в название выходного файла. fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(SectionName, 1, 4, BNDFileIn); CurrentPosition+=4; //Ещё на 4 байта вперёд, читаем длину секции. fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&ByteBuffer, 1, 4, BNDFileIn); SectionLenth = ByteBuffer[3] | (ByteBuffer[2] << 8) | (ByteBuffer[1] << 16) | (ByteBuffer[0] << 24); //Здесь начинается цикл, который разделит нам файл на отдельные секции. //Файлы с названием F00x - это спрайты, пожатые, похожим на LZX, алгоритмом. Без дополнительной информации(длина\ширина), //зашито в игровой движок в анимационных струкрурах CurrentPosition+=4; //Ещё на 4 байта вперёд, приступаем к циклу. fseek(BNDFileIn, CurrentPosition, SEEK_SET); SectionLenth=SectionLenth+CurrentPosition; int CurrentSection; int CurrentByte; CurrentSection = 1; while(true) { if(CurrentSection > SectionNumbers) { break; } //Если все секции раскомпанованы, то заканчиваем цикл. BNDFileOut = fopen(SectionName, "a+b"); //надо собирать название. if(BNDFileOut == NULL) { cout << "Out File is not enough!!" << endl; break; } while(true) { fread(&CurrentByte, 1, 1, BNDFileIn); //побайтово читаем fwrite(&CurrentByte, 1, 1, BNDFileOut); //Побайтово пишем CurrentPosition++; fseek(BNDFileIn, CurrentPosition, SEEK_SET); //Объяснение, чтобы не забыть. В файле прямой порядок чтения байтов(не обратный!) //С этим и связано побайтовое чтение. Очередное упрощения для себя if(CurrentPosition == SectionLenth) //Если достигнут конец секции { CurrentSection++; //CurrentPosition++; fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&SectionName, 1, 4, BNDFileIn); //Читаем название новой секции CurrentPosition+=4; //Ещё на 4 байта вперёд, читаем длину секции. fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&ByteBuffer, 1, 4, BNDFileIn); SectionLenth = ByteBuffer[3] | (ByteBuffer[2] << 8) | (ByteBuffer[1] << 16) | (ByteBuffer[0] << 24); CurrentPosition+=4; //Ещё на 4 байта вперёд, новый заход fseek(BNDFileIn, CurrentPosition, SEEK_SET); SectionLenth=SectionLenth+CurrentPosition; break; } } } if(ProgrammMode == 1)//только для файлов B16,извлечение палитры 16 бит. { CurrentPosition-=8; fseek(BNDFileIn, CurrentPosition, SEEK_SET); fread(&SectionName, 1, 4, BNDFileIn); //Читаем название новой секции CurrentPosition+=4; fread(&ByteBuffer, 1, 4, BNDFileIn); ColorNumbers = ByteBuffer[3] | (ByteBuffer[2] << 8) | (ByteBuffer[1] << 16) | (ByteBuffer[0] << 24); CurrentPosition+=4; SectionLenth=ColorNumbers+CurrentPosition; BNDFileOut = fopen(SectionName, "a+b"); //Секция с цветами while(true) { if(CurrentPosition >= SectionLenth) { break; } //Если все секции раскомпанованы, то заканчиваем цикл. if(BNDFileOut == NULL) { cout << "Out File is not enough!!" << endl; break; } fread(&CurrentByte, 1, 1, BNDFileIn); //побайтово читаем fwrite(&CurrentByte, 1, 1, BNDFileOut); //Побайтово пишем CurrentPosition++; fseek(BNDFileIn, CurrentPosition, SEEK_SET); } } fcloseall(); //закрываем все файловые потоки. system("pause"); // Только для тех, у кого MS Visual Studio return 0; } Декомпрессор CODE /* FDecompressor - Декомпрессор спрайтов(секций F000\1\2). Очень похож на LXZ, только обратный. Оригинальный код взят у -=CHE@TER=-, он отловил процедуру. Ниже после программы в комментариях будет функция PicDecoder, откуда собственно и взят сам алгоритм декомпресии. Немного из истории - данные секции не имеют стандартных "длины" и "ширины" изображения, все параметры забиты в исполняемом файле игры. Все данные собираются в единое в анимационных структурах.Одна секция может содержать в себе сразу несколько штук спрайтов. Если программа увидит, что длина позиции не в конце, то механизм декомпресии запустится заного.Декомпрессованые изображения спокойно смотрятся через программу TiledGGD(вьювер сырых данных) - для корректного отображения цвета в качестве палитры выбирайте секцию C000(Color), формула такой секции - 2 байта на цвет. Декомпановщик BNDExtractor извлекает её, если встречает B16 файл. Для BND файлов применяется общая "зернистая" палитра видимо из GUNPALS.pal [code]#include "stdafx.h" #include <math.h> #include <iostream> #include <fstream> #include <string> #include <stdlib.h> #include <pstdint.h> #include <cstdlib> // для system using namespace std; int main(int argc, char *argv[]) { char *FileName = argv[1]; char OutPutFileName[5]; //Нужно, чтобы собиралось название, если спрайтов больше одного OutPutFileName[0] = 'D'; OutPutFileName[1] = '0'; OutPutFileName[2] = '0'; OutPutFileName[3] = '0'; OutPutFileName[4] = '\0'; //Это чтобы собиралось название, обрубает всё "нулём". FILE* InputFile; //Входной файл FILE* OutputFile;//Выходной файл InputFile = fopen(FileName, "rb"); int CurrentPosition = 0; int OutFilePosition = 0; unsigned char CurrentByte; unsigned char OutputByte; fseek(InputFile, 0, SEEK_END); int LengthOfFile = ftell(InputFile); int i, offs, size, CurrentNameNumber; int FileLenthResult = LengthOfFile-2; //Главный цикл, проверяет конец файла и запускает механизм декомпресии while(true) { if(CurrentPosition >= (LengthOfFile-2)) //Если указатель уехал в конец, то выходим из главного цикла и завершаем работу { cout << "End File" << endl; break; } OutputFile = fopen(OutPutFileName, "a+b"); //Создаём выходной файл if(OutputFile == NULL) { cout << "Error, can't create file" << endl; break; } i = 0; offs = 0; size = 0; fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); while (1) { //-----------------------------------------// // while (1) { i >>= 1; // Сдвиг вправо if (!(i & 0xFF00)) // если старший байт ноль { i = 0xFF00 | CurrentByte; // то заменяем на 0xFF //конкатенация с текущим байтом CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); // и переходим к следующему байту во входном файле fread(&CurrentByte, 1, 1, InputFile); } if (i & 1) // если младший бит зажжён - выходим из этого цикла { break; } OutputByte = CurrentByte; // если нет, то копируем байт входного файла в выходной fwrite(&OutputByte, 1, 1, OutputFile); CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); // и двигаем указатель во входном файле на следующую позицию fread(&CurrentByte, 1, 1, InputFile); } //-----------------------------------------// //В начале цикл вываливается с символа P(50), после третьего захода(?). if (CurrentByte >= 96) { offs = CurrentByte - 256; size = 3; CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); // и переходим к следующему байту во входном потоке fread(&CurrentByte, 1, 1, InputFile); } else { //Это блок, отвечающий за повторы, сколько раз повторить и на какой дистанции взять пиксель. Словарём являются выходные данные(Выходной файл) size = (CurrentByte & 0xF0) >> 4; offs = (CurrentByte & 0x0F) << 8; CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); offs |= CurrentByte; CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); if (!offs) //не нуль? Это условие срабатывает, если встретился конец спрайта. { if(CurrentPosition != (LengthOfFile-2)) //Если указатель не приехал в конец файла, то надо запустить процесс по новой, собираем новое название { CurrentNameNumber =(int)(OutPutFileName[1] -0x30) *100 +(int)(OutPutFileName[2] -0x30) *10 +(int)(OutPutFileName[3] -0x30) +1;// собираем число +1 OutPutFileName[1] =(char)(CurrentNameNumber /100) +0x30; OutPutFileName[2] =(char)((CurrentNameNumber /10) %10) +0x30; OutPutFileName[3] =(char)(CurrentNameNumber %10) +0x30; while(CurrentByte == 0 && !feof(InputFile))//Если случайно не встретился конец файла и текущий байт нуль(разделительные нули между спрайтами) { CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); } } break; } offs = -offs; //-1? if (size == 5) { size = CurrentByte + 9; //48 CurrentPosition++; fseek(InputFile, CurrentPosition, SEEK_SET); fread(&CurrentByte, 1, 1, InputFile); } else { size = size + 4; } } while (--size) //Скользящее окно, формула - повторять size-раз на offs расстояние предыдущих пикселей { OutFilePosition = ftell(OutputFile) + offs; //Вычисляем предыдущие пиксели относительно текущей позиции в выходном файле fseek(OutputFile,OutFilePosition, SEEK_SET);//Ставим туда указатель fread(&OutputByte, 1, 1, OutputFile);//Читаем от туда байт fseek(OutputFile, 0, SEEK_END);//Ставим указатель в конец fwrite(&OutputByte, 1, 1, OutputFile);//Пишем этот байт и запускаем цикл заного. } } } fcloseall(); //Закрываем все потоки. system("pause"); return 0; } |
-=CHE@TER=- |
Nov 17 2020, 11:15
Сообщение
#20
|
Walter Sullivan Группа: Root Admin Сообщений: 1,361 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 314 раз(а) |
Недоработка: все файлы на выходе дозаписываются в конец, перед повторным использованием убирайте старые декомпанованые файлы. Так ты "wb" (w = write, b = binary) делай, а не "a+b" (a = append to existing, + = modify, b = binary), когда выходной файл создаёшь при помощи fopen(). |
Упрощённая версия | Сейчас: 14th November 2024 - 10:45 |