Версия для печати темы

Нажмите сюда для просмотра этой темы в оригинальном формате

CTPAX-X _ Ресурсы _ Alien Trilogy (PC)

Автор: LexSafonov Oct 29 2020, 17:48

Всем привет, решил создать тему про разбор форматов ресурсов игры 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/f000_format-png.211586/
немного наглядности из примерного описания формата, файл MM9.B16

По поводу формата повторений. У него тоже есть определённые условия, а именно, описание верно только для одного условия - когда первое число после префикса равно 1. Видимо для игры это обозначает рисовать цвет сплошняком. Плюс изначально для сплошного цвета игра прибавляет в ряд толи 8, толи 10 неубираемых пикселей. Я насчитал 10.
В файле присутствуют варианты, когда первый байт после префикса больше единицы. Из своих экспериментов пока только сделал вывод, что что-то двигается(из пикселей), но не понял закономерности. Иногда бывает, что в повторениях походу висят какие-то ссылки на другие места. Кстати об этом тоже по подробнее.

В один момент я решил разбирать вообще сплошняком побайтово, но запутался ещё больше:
https://www.old-games.ru/forum/attachments/010table-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

Ну, я уже писал, что смотрел игру бегло, там реально какое-то замороченное сжатие. Причём, на мой взгляд, его явно перемудрили.
Кстати, по поводу форматов файлов с ресурсами, всё забываю сказать - они используют http://www.shikadi.net/moddingwiki/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

Так, продолжаю работу по просмотру сырого дизассемблированного кода и параллельно отладочного. Вообщем решил посмотреть, как в IDA вообще обзывается указатель от пистолета, который ты мне скинул. И обнаружил собственно вот это:
http://piccy.info/view3/14046820/c9816e8b7ad19e39022b268dd3f2a5c2/1200/http://i.piccy.info/a3c/2020-10-30-19-26/i9-14046820/800x450-r
По началу при беглом просмотре этих строк я и не думал, что это относится к кадрам, но сейчас до меня походу допёрло от чего это всё добро - это видимо что то от "анимационных структур". Я когда то находил жирную процедуру, которая "собирала" эти данные. Там даже есть указатели на текст ошибок, вроде как с жалобой на память.
Есть теперь повод побаловаться и понаблюдать, что каждый байт делает. Может что то найдётся по распаковке, ведь там есть непонятные блоки-сегменты. Я подозреваю, что там могут быть ещё стандартные параметры, типа скорости кадра(сколько тиков он висит), какие то действия в кадре(выстрел\звук). Если мои догадки подтвердятся, то можно будет даже побаловаться со стандартными параметрами.

Заодно решил бегло пробежаться по мануалу коробочного отладчика и нашёл способ, как остановить выполнение программы на этапе обращения к файлам через комманду BPINT 21 42. Сразу оговорюсь, что я нифига не понял про прерывания, но способ полезный, тормозит игру как раз, когда нужно.
Сверялся с кодом из IDA Pro, пока нашёл одну процедуру в отладчике, которую не смог отыскать в дизассемблере.
http://piccy.info/view3/14046814/71c0d16d96c06a2da716d90b02333183/1200/http://i.piccy.info/a3c/2020-10-30-19-19/i9-14046814/800x450-r
Наверное связано с тем, что я начал отлаживать только с третьего захода, когда открыло уже три файла.
Пока отключаюсь, завтра ещё один заход сделаю, может что то интересное найду.

Автор: -=CHE@TER=- Oct 30 2020, 20:24

QUOTE(LexSafonov @ Oct 30 2020, 19:34) *
По началу при беглом просмотре этих строк я и не думал, что это относится к кадрам, но сейчас до меня походу допёрло от чего это всё добро - это видимо что то от "анимационных структур". Я когда то находил жирную процедуру, которая "собирала" эти данные. Там даже есть указатели на текст ошибок, вроде как с жалобой на память.
Да, возможно. См. моё письмо - там с этого смещения и ниже (я писал в письме смещение) всё к пистолету относится. Там несколько структур.

QUOTE(LexSafonov @ Oct 30 2020, 19:34) *
Заодно решил бегло пробежаться по мануалу коробочного отладчика и нашёл способ, как остановить выполнение программы на этапе обращения к файлам через комманду 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.

QUOTE(LexSafonov @ Oct 30 2020, 19:34) *
Сверялся с кодом из IDA Pro, пока нашёл одну процедуру в отладчике, которую не смог отыскать в дизассемблере.
Наверное связано с тем, что я начал отлаживать только с третьего захода, когда открыло уже три файла.
Пока отключаюсь, завтра ещё один заход сделаю, может что то интересное найду.
Ты куда-то ушёл не туда - это уже область данных. В частности, ты стоишь на строке "0123456789abcd", которую дизассемблер пытается как-то дизассемблировать.

Автор: LexSafonov Oct 31 2020, 09:46

Так, сегодня сел со вежей головой и решил прикинуть устройство анимаций оружий. Вообщем набросал я вот что:
http://piccy.info/view3/14047217/0d3bd75f0db2e3018cc6c41712126259/1200/http://i.piccy.info/a3c/2020-10-31-09-19/i9-14047217/800x450-r
Из своего опыта по конверсиям я прикинул вот такое описание. У оружий есть состояния, а именно:
Idle - простой\бездействие
Fire - огонь
AltFire - альтернативный огонь, нашёл только у пульсатора. Выстрел подствольником.
Reload - "перезарядка"

Забавный факт, у огнемёта тоже видимо есть состояние перезарядки, но видимо игра это поле тупо игногрит. Наверное это когда то начали делать и потом забили, а вычищать банально не стали, т.к. "никто же не полезет туда"))))
У дробовика только 2 состояния - простой и выстрел, т.к. спрайты перезарядки дробовика рисуются сразу после выстрела.
У пульсатора самое большое кол-во состояний из-за альтернативной стрельбы подствольником.

У кадого состояния есть 2 поля - в одном(без приписки Pic) описывается размер картинки и какие то другие параметры(вполне вероятно может быть офсет на самом экране, но это не точно). Может содержать данные на несколько картинок, по очереди.
Во втором видимо что-то по скоростям и действиям(вчера ночью я умудрился сделать так, что спрайт пистолета вообще самоуничтожился через какое то время в белый прямоугольник и игра вылетела).
Сразу сделаю оговорку, в полях с припиской Pic я пока не разобрался(я просто это обозвал так, чтобы не путаться).

Сегодня на досуге ещё по монстрам пробегусь, я так думаю там есть состояния не только простоя, но и различных атак, и даже наверное что то по "углам" относительно взгляда игрока(какие то состояния для рендера).

Автор: -=CHE@TER=- Oct 31 2020, 17:22

Всё, я нашёл функцию декодирования.
Ищешь в исполняемом файле следующую последовательность байт:
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

Блин, бро, да ты мозг! Прям интересно стало, что там вывертеть могли...
Проверка возможно связана с тем, что в 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

QUOTE(LexSafonov @ Nov 1 2020, 08:11) *
Проверка возможно связана с тем, что в BND вариантах лежат "зернистые" варианты спрайтов (режим 256 цветов).
Вряд ли, алгоритм сжатия там, скоре всего, один. Второе сжатие, скорее всего, для другого типа данных.

QUOTE(LexSafonov @ Nov 1 2020, 08:11) *
// Не понял для чего сдвиг вправо, там же по идее в переменной нуль.
Для первого прохода не важно. Это для следующих.

QUOTE(LexSafonov @ Nov 1 2020, 08:11) *
// Не догоняю, что обозначает 0xFF00 и что там пытаются проверить.
Что старший байт ноль.
i & 0xFF00 - будет равно 0x##00, где ## - старший байт в слове. Далее:
QUOTE(LexSafonov @ Nov 1 2020, 08:11) *
//Тоже не понял для чего оно.
Если старший байт в слове ноль, то он заменяется на 0xFF.
Например, так:
0x0011 => 0xFF11
0x0034 => 0xFF34

QUOTE(LexSafonov @ Nov 1 2020, 08:11) *
Это то пресловутое "скользящее окно"?
Скользящее окно - это нижний цикл "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

Так, ещё порция глупых вопросов, по поводу того алгоритма декомпресии. В функцию надо передавать указатель на файл? Или же надо файл считать в массив элементов и потом в качестве параметра указать его?(Имеется ввиду *p) Аналогично для второго параметра, который отвечает за данные на выходе.
И ещё столкнулся с одной проблемой - как в сишке тип uint8_t подключить? Есть ли заголовочные файлы в самой студии для таких типажей?

Начал с простого, сделал простенький "раскомпоновщик" BND файлов на отдельные секции. Думаю через код декомпресии прогонять эти раскомпонованые файлы. Не знаю, правильно или нет, делаю как можно проще, из-за малого опыта.

Автор: -=CHE@TER=- Nov 11 2020, 12:59

QUOTE(LexSafonov @ Nov 11 2020, 08:21) *
В функцию надо передавать указатель на файл? Или же надо файл считать в массив элементов и потом в качестве параметра указать его?(Имеется ввиду *p) Аналогично для второго параметра, который отвечает за данные на выходе.
Так я же писал, что это буфер (оба параметра):
QUOTE(-=CHE@TER=- @ Oct 31 2020, 17:22) *
Первый параметр (p) - указатель на сжатые данные, второй (u) - на буфер куда распаковывать.


QUOTE(LexSafonov @ Nov 11 2020, 08:21) *
И ещё столкнулся с одной проблемой - как в сишке тип 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

Ух ты. Даже как то не по себе, от того, что ты прогу написал smile.gif
Я вообще думал как сделать - отдельная прога "раскомпановщик", потом отдельная прога "декомпрессор" и уже последняя для просмотра "сырых" декомпрессованых данных, где можно было бы на ходу забивать длину, ширину и смещение от начала файла.
Там вроде есть секции, в которых содержится сразу несколько спрайтов и, бывает, разных размеров.

У BND там палитра какая то стрёмная, чувак с ником ak47 описывал свои наблюдения, как она работает. С его слов я понял только, что один файл с палитрой имеет в себе несколько "наборов" на файлы со спрайтами, строго фиксированные. Я это не смотрел, т.к. всё время пытался разобрать формат B16 и цвета я сразу в первое время нашёл(от того и надобности не было в поиске палитры).

Ты уж извини за глупые вопросы. В качестве "домашней работы" я таки попробую по своему методу, простыми линейными прогами, а потом уже, если получится, попробую что-то по серьёзнее biggrin.gif

Автор: -=CHE@TER=- Nov 11 2020, 18:02

QUOTE(LexSafonov @ Nov 11 2020, 14:09) *
Там вроде есть секции, в которых содержится сразу несколько спрайтов и, бывает, разных размеров.
Ну, вот, тебе будет что допиливать.

QUOTE(LexSafonov @ Nov 11 2020, 14:09) *
Я это не смотрел, т.к. всё время пытался разобрать формат B16 и цвета я сразу в первое время нашёл(от того и надобности не было в поиске палитры).
Кстати, как я и говорил - сжатие у .B16 и .BND одинаковое.

QUOTE(LexSafonov @ Nov 11 2020, 14:09) *
Ты уж извини за глупые вопросы.
Нормальные вопросы. Что ты всё время извиняешься-то.

QUOTE(LexSafonov @ Nov 11 2020, 14:09) *
В качестве "домашней работы" я таки попробую по своему методу, простыми линейными прогами, а потом уже, если получится, попробую что-то по серьёзнее biggrin.gif
Ради бога - как тебе будет удобнее.

Автор: LexSafonov Nov 13 2020, 16:01

Так, сделал простую прогу, линейную, для декомпресии. Но столкнулся вот с такой картиной:
http://piccy.info/view3/14067739/6ffd83768c02fa6d1ecf01d880aac2c9/1200/http://i.piccy.info/a3c/2020-11-13-15-40/i9-14067739/800x450-r

Пришлось чутка переделать функцию, ну и параллельно изучить, как она работает с данными.
Я сделал собственно вот так:

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

QUOTE(LexSafonov @ Nov 13 2020, 16:01) *
А блин, понял, я же неправильно скользящее окно сделал... Там же напрямую пишет откуда данные брать...
Ну, у тебя неправильно скользящее окно сделано. Когда смещение делается отрицательным (offs = -offs), то это означает что у тебя на текущую (последнюю) позицию в выходном буфере / файле пишется байт с позиции -offs символов назад. Вот это выражение, которое "*u = u[offs]".
LZX же как работает - если у тебя встречается какая-то последовательность байт, которая уже есть где-то в начале и не сильно далеко (иначе не хватит байт для кодирования расстояния), то сохраняется ссылка назад на то место и какое количество байт повторяется (сколько скопировать оттуда) - за это как раз отвечают собранные из байт потока переменные offs (offset - смещение) и size (size - размер).

По мелочи:
QUOTE(LexSafonov @ Nov 13 2020, 16:01) *
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" и стоят, чтобы хоть где-то, по какому-то условию, выход был.

QUOTE(LexSafonov @ Nov 13 2020, 16:01) *
system("pause"); // Только для тех, у кого MS Visual Studio
Будет работать и в GCC и в других компиляторах, потому что system() - это библиотечная функция вызова команды из операционной системы. Например, попробуй написать:
system("calc");
И посмотри что получится.

Автор: LexSafonov Nov 13 2020, 17:51

Получилось! Переделал немного, сделал выходной буффер и с него уже записывал в файл, чтобы не нарушать математику процесса smile.gif
http://piccy.info/view3/14067907/33ff6e1c328d5c13251b17bed39f5a41/1200/http://i.piccy.info/a3c/2020-11-13-17-41/i9-14067907/800x450-r
Можно наверное это опубликовать всё на олд-геймсе(исходные коды, проги), если ты не против.
Кто захочет посмотреть, он и через TiledGGD посмотрит, а я сосредоточусь на простеньком аналоге этого самого вьювера сырых данных)

Автор: -=CHE@TER=- Nov 13 2020, 20:54

QUOTE(LexSafonov @ Nov 13 2020, 17:51) *
Получилось!
Во, молодец!

QUOTE(LexSafonov @ Nov 13 2020, 17:51) *
Можно наверное это опубликовать всё на олд-геймсе(исходные коды, проги), если ты не против.
Лучше просто ссылку на эту тему дать.

QUOTE(LexSafonov @ Nov 13 2020, 17:51) *
Кто захочет посмотреть, он и через TiledGGD посмотрит, а я сосредоточусь на простеньком аналоге этого самого вьювера сырых данных)
Если ты про свой код - публикуй где хочешь и как хочешь конечно. Это же ты его написал.

Автор: LexSafonov Nov 13 2020, 22:15

На счёт исходных кодов - и твои и мои. Если ты не против конечно.
Ссылку выложу. Я нашёл один нюанс, кажись понял как делятся секции на спрайты. Общий цикл вываливается на условии:
if (!offs) { break; }
Как я понял это условие срабатывает, когда встречается пару нулей подряд. Сравнивал с hex-кодом - и вправду. Надо добавить будет условие на повторный запуск цикла, если спрайтов больше одного(ну и поиграться с указателем). Либо, если не получится, то написать отдельный декомпановщик секций...
Завтра подумаю над этим, сейчас уже голова чёт не варит.

Автор: -=CHE@TER=- Nov 14 2020, 12:05

QUOTE(LexSafonov @ Nov 13 2020, 22:15) *
На счёт исходных кодов - и твои и мои. Если ты не против конечно.
Ссылку выложу.
Я не против. Но про ссылку почему упомянул - если кому-то детали понадобятся, то здесь они описаны, потому что смотреть только исходные коды гораздо сложнее.

Автор: LexSafonov Nov 16 2020, 19:24

И так, после долгого мучения и раздумий, я наконец заставил мои 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

Ну и пару скриншотов на последок, для затравки))
http://piccy.info/view3/14072839/8a081127b5c2b5233b26f1fdb7d4584e/1200/http://i.piccy.info/a3c/2020-11-16-19-13/i9-14072839/800x450-r
http://piccy.info/view3/14072840/e0490097c7507408f885cc9440c2bd3e/1200/http://i.piccy.info/a3c/2020-11-16-19-14/i9-14072840/800x450-r

Обе программы чисто исходными кодами(если вдруг файл снесут с хостинга, можно будет скомпилить в 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

QUOTE(LexSafonov @ Nov 16 2020, 19:24) *
Недоработка: все файлы на выходе дозаписываются в конец, перед повторным использованием убирайте старые декомпанованые файлы.
Так ты "wb" (w = write, b = binary) делай, а не "a+b" (a = append to existing, + = modify, b = binary), когда выходной файл создаёшь при помощи fopen().

Автор: LexSafonov Nov 17 2020, 13:39

QUOTE(-=CHE@TER=- @ Nov 17 2020, 11:15) *

Так ты "wb" (w = write, b = binary) делай, а не "a+b" (a = append to existing, + = modify, b = binary), когда выходной файл создаёшь при помощи fopen().

А функция fwrite() жаловаться не будет? У меня же все данные в основном в конец дозаписываются

Автор: -=CHE@TER=- Nov 17 2020, 16:04

QUOTE(LexSafonov @ Nov 17 2020, 13:39) *
А функция fwrite() жаловаться не будет? У меня же все данные в основном в конец дозаписываются
Какая разница куда у тебя данные записываются, если файл открыт для записи? Если ты помимо записи что-то читаешь или по файлу туда-сюда ходишь, то сделай "w+b".
И ещё ты делаешь главную ошибку: вместо того чтобы попробовать и посмотреть как будет работать, ты задаёшь вопрос. А так у тебя уйдёт 1 минута чтобы проверить или 5 чтобы спросить на форуме.
Если бы жёсткий диск форматировал или человека в космос запускал - переспрашивать и перепроверить было бы логично для надёжности, ибо последствия могут быть необратимыми. А смысл это здесь делать?
Добавлю: я твой код не смотрел, так что если ты несколько файлов в один зачем-то пишешь, тогда тебе нужно первый создавать с "w+b", а остальные уже с добавлением.

Автор: LexSafonov Nov 17 2020, 16:39

QUOTE(-=CHE@TER=- @ Nov 17 2020, 16:04) *

Какая разница куда у тебя данные записываются, если файл открыт для записи? Если ты помимо записи что-то читаешь или по файлу туда-сюда ходишь, то сделай "w+b".
И ещё ты делаешь главную ошибку: вместо того чтобы попробовать и посмотреть как будет работать, ты задаёшь вопрос. А так у тебя уйдёт 1 минута чтобы проверить или 5 чтобы спросить на форуме.
Если бы жёсткий диск форматировал или человека в космос запускал - переспрашивать и перепроверить было бы логично для надёжности, ибо последствия могут быть необратимыми. А смысл это здесь делать?
Добавлю: я твой код не смотрел, так что если ты несколько файлов в один зачем-то пишешь, тогда тебе нужно первый создавать с "w+b", а остальные уже с добавлением.


Ладно, исправлю, не ругайся только smile.gif

Автор: -=CHE@TER=- Nov 17 2020, 17:18

QUOTE(LexSafonov @ Nov 17 2020, 16:39) *
Ладно, исправлю, не ругайся только smile.gif
Я не ругаюсь, у меня просто голос такой. Кстати, вспомнился, https://www.extractor.ru/ipb/index.php?showtopic=1666&page=2&#entry11846 13 лет назад. Время летит - офигеть можно.
Смысл в том, что вопрос нужно задавать, когда ты уже сам всё перепробовал и упёрся в тупик и не понимаешь что делать дальше.
Или сделал так-то, но сомневаешься и хочешь узнать можно ли сделать лучше.
И тому подобные случаи.
Я ж говорю - я готов помочь, мне не жалко.
Просто человек когда сам до чего-то доходит, то лучше усваивает материал: лучше запоминает, плюс в схожей ситуации попробует так же сделать - решение уже будет само напрашиваться.

Автор: LexSafonov Nov 19 2020, 09:33

Так, решил своим же инструментом попробовать извлечь все спрайты из игры, и наткнулся на пару стрёмных багов. А именно:
- декомпановщик пару файлов неправильно раскомпановывает(COLONIST.B16 и SYNTH.B16), причину пока не понял, вроде бы какие то файлы создаёт на выходе, при чём полностью рабочие, но пару раз показывает ошибку на то, что не может создать файл. На досуге разберусь в чём дело.
- декомпрессор неправильно декомпрессует секции в файле BURSTER.B16. Позже обнаружил, что в одной секции есть непожатый спрайт, при чём в конце. Надо будет тоже исправить.

Пока извлекал ресурсы, нашёл неиспользуемые спрайты перезарядки для огнемёта
http://piccy.info/view3/14076861/38c07302a3d0fbfd4dd0617682370382/http://i.piccy.info/a3c/2020-11-19-09-05/i9-14076861/64x72-r
http://i.piccy.info/i9/7c1602f3294a292c00fbed73a95494b6/1605776726/4712/1403118/D021.png
http://i.piccy.info/i9/599c75347dafe5dff2981547c815e5bd/1605776741/5913/1403118/D022.png
Это видимо, то, что я нашёл не так давно в анимационных структурах(поле, которое игнорится).

Есть ещё несколько интересных спрайтов у взрослого чужого (файл WARCEIL.B16), складывается ощущение, что этот типаж мог не только лазить по потолку, но и залазить на этот самый потолок с пола(И видимо обратно и неоднократно).
В оригинальной игре ни разу не видел подобного. Пример:
http://i.piccy.info/i9/981499a7785b2690d5aa1c3abc5e7d47/1605777172/1844/1403118/D040.png
http://i.piccy.info/i9/a5783dfe48405e2edf3ea7d0360931e1/1605777195/1902/1403118/D041.png
http://i.piccy.info/i9/f4ec6a3d75c3b5a9ff50ee4e527886f2/1605777208/2402/1403118/D042.png
http://i.piccy.info/i9/a6e3b839e820d3e2d531c6c0090bf40b/1605777223/2366/1403118/63603D043.png

Параллельно решил пробежаться по формату MAP и сопоставить его с форматом моделек.
Карта трёхмерная, видимо так же, как и модель, состоит из квадов и вершин. Но устройство немного другое.

Содержит в себе такие секции:
MAP0 - геометрия карты(и не только)
D000\1\2\3 - видимо динамические объекты. По первой карте я пока только понял, что это двери.(Два типажа, первая дверь - на старте и в конце уровня, вторая - промежуточная, везде одна и та же модель применяется). Формат как у обычных моделей.
Текстурная сетка для карты содержится в B16 файлах карты рядом с текстурой. Секция BX00.
По поводу устройства формата скриншот для наглядности:
http://piccy.info/view3/14076847/bb585adf0228f77075a63edd498b6bba/1200/http://i.piccy.info/a3c/2020-11-19-08-53/i9-14076847/800x450-r
Там есть 32 байта непонятные поля. Пока не понял, какие значение могут отвечать за общее кол-во вершин. Если брать по 4 байта на длину, то значение получается какое то огромное(несколько миллионов, по факту всего несколько сотен)
Вершины я пробовал обнулять, чтобы посмотреть реакцию игры - работает, геометрию начинает искривлять местами.

Автор: -=CHE@TER=- Nov 19 2020, 14:43

QUOTE(LexSafonov @ Nov 19 2020, 09:33) *
Пока извлекал ресурсы, нашёл неиспользуемые спрайты перезарядки для огнемёта (...) Есть ещё несколько интересных спрайтов у взрослого чужого (файл WARCEIL.B16), складывается ощущение, что этот типаж мог не только лазить по потолку, но и залазить на этот самый потолок с пола(И видимо обратно и неоднократно).
В оригинальной игре ни разу не видел подобного.
Перед выпуском зачастую забывают вычистить рабочие файлы или оставшиеся неиспользуемыми. Иногда какую-то вещь до самого последнего момента пытаются починить или вставить в игру, но из-за ошибок или проблем которые она создаёт в итоге перед самым выпуском просто в коде отключают. А ресурсы, конечно, остаются.

QUOTE(LexSafonov @ Nov 19 2020, 09:33) *
Там есть 32 байта непонятные поля. Пока не понял, какие значение могут отвечать за общее кол-во вершин. Если брать по 4 байта на длину, то значение получается какое то огромное(несколько миллионов, по факту всего несколько сотен)
Вершины я пробовал обнулять, чтобы посмотреть реакцию игры - работает, геометрию начинает искривлять местами.
Возможно, там дробные числа с фиксированной точкой, т.е. когда первые два байта это целая часть, а вторые - дробная часть после запятой. Бывают и просто дробные числа в 4 байта (float), но т.к. игра портировалась на консоли, а с дробными часлами там всё тогда было очень плохо (тупо не было), то я поставил бы всё же на дробные с фиксированной точкой. Хотя, конечно, на PC могли что-то моменять, но тут смотреть нужно. Чтобы понять о чём речь рекомендую https://theforceengine.github.io/2020/08/07/FixedPointAndHigherResolutions.html - она про другую игру, но общее представление о том как выкручивались в то время понять можно.

Когда работаешь со старыми играми или программами зачастую это не столько дизассемблирование, сколько археология получается, ибо некоторые решения в форматах и общей архитектуре обоснованы ограниченными возможностями оборудования того времени.

Автор: LexSafonov Nov 19 2020, 20:10

Так, продолжаю свои разборы формата карты. Ещё раз решил перепроверить себя, сопоставил снова с форматом моделей. Нашёл кажись квады:
http://piccy.info/view3/14077767/58399f551b0bd2fdddda1174aef7306b/1200/http://i.piccy.info/a3c/2020-11-19-19-02/i9-14077767/800x450-r

Формат квадов у моделей такой:
0x04 - индекс первой точки. LITTLE-ENDIAN
0x04 - вторая точка
0x04 - третья точка
0x04 - четвёртая точка. может быть -1 (0xFFFFFFFF), тогда это треугольник и надо продублировать третью точку.
0x02 - индекс текстуры квада (см. формат PICKGFX.BND дальше)
0x02 - некий параметр. может быть 11, тогда текстуру надо перевернуть по Y (?). может быть 128. неизвестно зачем.

Как я понял, что это квады: в файле есть индексы FFFFFFFF. По ним я начал тупо идти наверх в файле, пока не упёрся в место, отмеченное на скриншоте. Логика рушится на 4-х байтах отмеченных синим цветом.
В цепочке байт - 00 FA 00 08 00 25 00 00
00 FA 00 08 - не может быть индексом, больно число большое.
00 25 00 00 - вполне тянет на индекс(9472)
Под описание подходят вершины, но их кол-во чёт больно большое(25 тысяч примерно, если общее кол-во разделить на 4, по формату вершины).

На счёт дробных чисел, в формате моделей применяются целые значения. Скорее всего в формате карт то же самое...




Автор: LexSafonov Nov 20 2020, 15:08

Обновляю данные по поводу формата карт.
http://piccy.info/view3/14078700/1c9ea02a954648eb80141bfa240ba855/1200/http://i.piccy.info/a3c/2020-11-20-14-41/i9-14078700/800x450-r

Вообщем по поводу вершин и квадов был прав, но не допирал каким образом всё расчитывается, т.к. одно место меня упрямо сбивало с толку. После поля с квадами есть поле, в котором полно байт FFFFFFFF, но отвечает оно за что то другое, пробовал обнулять, изменений на карте не заметил(либо я не понял, что поменялось)
Теперь небольшое объяснение. Кол-во вершин и квадов забито в файле порядком Little-Endian(в обратном).
Вершины:
C0 2F - В little-endian это 12224, умножаем на 8. 8 байт один элемент - 6 байт на 3 координаты XYZ и 2 байта пустых, как я понял, это сделано, чтобы сделать данные кратными двойке. После применения данной формулы как раз приходим точно в начало поля с квадами
Квады:
02 2С - В little-endian это 11266, умножаем на 20. 20 байт один элемент - 16 байт на 4 индекса точек и 4 байта с информацией о текстурной сетке и режиме отображения.

Данные расчёты я проверил на трёх разных файлах с картами, всё сходится. Для извлечения геометрии вполне этого может хватить, но карта будет везде светлая, т.к. пока не ясен принцип работы света - вроде бы есть какие то источники, но динамические они или нет - не ясно. По мере выяснения буду дополнять информацию о формате карт.

Автор: LexSafonov Nov 22 2020, 12:45

И так, сейчас будет хороший такой пост по разбору формата карт.
Для начала, разобранный заголовок на данный момент:
http://piccy.info/view3/14081660/b7f65af959e8c307f005f536d284f360/1200/http://i.piccy.info/a3c/2020-11-22-11-29/i9-14081660/800x450-r



Текущее описание заголовка формата карт




0x04 46 4F 52 4D FORM
0x04 - размер данных файла в байтах, BIG-ENDIAN
0x04 - количество карт в файле текстом.(обычно нуль, т.к. больше
одной карты в файлах не встречал)




- дальше идет информация о геометрии карты и её содержании, а именно:
размер содержимое комментарий
-------------------------------------------------------------------------------------------------------------------------
0x04 - Название карты, текст
0x04 - размер данных карты, BIG-ENDIAN(прямой порядок байт)
0х02 - Кол-во вершин?.LITTLE-ENDIAN(обратный порядок байт)
Формула - значение этих двух байт умножить на 8
(6 байт на 3 точки + 2 байта нули)




0x02 Кол-во квадов(прямоугольников).LITTLE-ENDIAN
(обратный порядок байт). Формула - значение этих 2-х байт
умножить на 20 (16 байт индексы точкек и 4 байта информация)




0x02 ------------ Длина "прямоугольника" мини карты(физ. движка)(Little-Endian)
0х02 ------------ Ширина "прямоугольника" мини карты(физ. движка)(Little-Endian)
Формула для этих байт = умножить длину на ширину и полученое значение умножить на 16
16 байт описывают одну ячейку. Пока что не расшифровал все значения одной ячейки.
Эти поля что то описывают для физического движка, осязаемость, высота.
Попробовал подменить пару блоков данными от "неосязаемых", и получилось - игра
убирает осязаемость и с миникарты убирает характерную "точку".
Исходя из этого можно предположить, что игра с трёхмерной картой практически
не взаимодействует.




0х06 - Непонятные значения




0х02 - Поле описания монстров, которые просто спавнятся.
Формула = кол-во элементов умножить на 20(20 байт на одного монстра)
0х02 - Поле описания пикапов, формула = кол-во элементов умножить на 8
0х02 - Поле описания "коробок" и подобных объектов(свитчей, бочек)
Формула = кол-во элементов умножить на 16




0x16 - Непонятные значения.

Теперь отдельно по форматам данных.




На самом первом скриншоте вершины отмечены.

- формат вершины:

размер содержимое комментарий

-------------------------------------------------------------------------------------------------------------------------

0x02 - координата X (signed, LITTLE-ENDIAN, short)

0x02 - координата Y

0x02 - координата Z

0x02 - неизвестное значение, вроде бы всегда 0



Следующий скриншот показывает "границу" - конец квадов и начала "блоков физ. движка".
http://piccy.info/view3/14081690/72a8af2002f5e91d1518687fac462863/1200/http://i.piccy.info/a3c/2020-11-22-11-42/i9-14081690/800x450-r




- формат прямоугольника:

размер содержимое комментарий

-------------------------------------------------------------------------------------------------------------------------

0x04 - индекс первой точки. LITTLE-ENDIAN

0x04 - вторая точка

0x04 - третья точка

0x04 - четвёртая точка. может быть -1 (0xFFFFFFFF), тогда это треугольник и надо продублировать третью точку.

0x02 - индекс текстуры квада

0x02 - некий параметр. Видимо что то по отображению.




Формат одного блока физ. движка пока не расшифровывал, подозреваю, что там есть что то по осязаемости, высоте, может быть какие то индексы для логики на карте.
Как и писал выше - я пробовал подменять данные от блоков, которые неосязаемые, данными от осязаемых и наоборот - эффекты есть и весьма ожидаемые.(Как и на автокарте)

Пока не ясен ещё один нюанс - какая система применяется в игре, пол-потолок(как в играх типа дум\дюк), или же блоки полностью трёхмерные. Но ясно одно точно - трёхмерная карта это чисто декорация. Очевидно связано с тем, чтобы не делать сложный физический движок и все ресурсы пустить на рендеринг(Если посчитать кол-во квадов на первой карте, то там уже 11 тысяч штук!).



Следующий скриншот показывает поле с описанием монстров
http://piccy.info/view3/14081727/fd7ce9c15a19408cf08b0c712e73725b/1200/http://i.piccy.info/a3c/2020-11-22-12-10/i9-14081727/800x450-r

Оранжевым цветом помечены лицехваты, светло-сиреневым помечены монстры, которые не заспавнились на карте, серым помечены взрослые чужие.
Возможно, монстры, отмеченные светло-сиреневым, зависят как то от уровня сложности, но это только предположение.




Формат данных одного монстра:

0х01 - Тип атаки?(Может быть и тип монстра)

0х01 - X координата?(Весьма странно описаны координаты, максимум 255? fixed числа?)

0х01 - Y координата?(Тем не менее монстра они двигают)

0х01 - Z координата?(Видимо FF обозначает отспавнить на полу)

0х02 - Кол-во хелс-поинтов(видимо в фиксед числе)

0x01 - Какой объект спавнит при смерти(индекс)

0х01 - Непонятный байт, если больше нуля, то монстра не спавнит

0х04 - Непонятные байты, не ясен эфффект от изменения.

0х02 - Непонятные байты, изменения не заметны(возможно угол поворота при спавне)

0х02 - Скорость перемещения объекта(монстра).

0х04 - Непонятные байты, при изменении не ясен эффект. Один и тот же монстр может

иметь разные значения


Ну и вдовесок к разбору монстров видос, где я изменил параметры лицехвата, который попадается на старте:
https://www.youtube.com/watch?v=NNl59yII4Ys

По поводу типажа монстра - пробовал подменить значение 02 на 08 - спавнит чужого, однако при выставлении значения 03 спавнит монстра, у которого не рендерятся спрайты, на вид атака похожа на ту, что у грудолома.

Следующий скриншот показывает 2 поля - поле с пикапами и поле с "коробками" - спец. объектами(бочками, коробками, свитчами).
http://piccy.info/view3/14081742/411f627a8b699dd7c5c0809e7767eebb/1200/http://i.piccy.info/a3c/2020-11-22-12-21/i9-14081742/800x450-r




Формат данных одного пикапа(По всей видимости 8 байт):


0х01 - Координата X?
0х01 - Координата Y?
0х01 - Тип пикапа, индекс
Типажи:
0 - пистолет
1 - дробовик
2 - пульсатор
3 - огнемёт
4 - смарт-ган
5 - не используется
6 - сейсмо-заряд
7 - батарея(для свитчей, использующих тип "батарея")
8 - очки ночного видения
9 - пистолетная обойма
10 - патроны для дробовика
11 - обойма для пульсатора
12 - подствольная граната для гранатомёта
13 - топливо для огнемёта
14 - обойма для смарт-гана
15 - идентификационная карта(которая вылетает из колонистов\охранников)
16 - автокарта
17 - гипер-пак
18 - броня (acid west), видимо защищает от кислоты
19 - броня простая
20 - аптечка
21 - бинт(+1 хелс)
22 - защитные ботинки
23 - адреналин
24 - бинт?
25 - солдатская лампа


Типажи выше можно поднять, следующие неподнимаемые(видимо использовали для отладки)
26 - гильза от дробовика
27 - гильза от пистолета
Значения выше 27(28 и далее), стабильно приводят к вылету.


0х01 - кол-во, которое выдаёт пикап, при поднятии
0х01 - какой то множитель для предыдущего значения
0х01 - непонятное значение, везде вроде нуль
0х01 - видимо Z координата
0х01 - тоже какое то кол-во для пикапа, непонятно как работает.




Формат "коробок"(видимо 16 байт), один нюанс, в поле коробок описываются не только коробки.
0х01 - Координата X?
0х01 - Координата Y?
0x01 - Тип "коробки"(Видимо и типаж и модель одновременно)
Есть такие типы:
19 - коробка, которую нельзя взорвать
20 - обычная коробка, которую можно взорвать
21 - пустой объект(на данный момент), может быть свитч для уровней на корабле
22 - ещё один мелкий свитч, отличие внизу у модели(нарисована молния)
23 - бочка, взрывается.
24 - мелкий свитч без молнии
25 - "двойная коробка"(две коробки друг над другом, которые можно взорвать)
26 - широкий свитч с молнией
27 - широкий свитч без молнии
28 - пустой объект, который можно прострелить
29 - пустой объект, который можно прострелить, что то спавнит при смерти
30 - обычная коробка, которую можно взорвать
0х01 - что объект заспавнит при смерти 00 - видимо пикап, 02 - видимо монстр
0х02 - пока не понял, что данные два байта делают
0х01 - видимо индекс объекта, который надо заспавнить
0х01 - видимо индекс второго объекта, который надо заспавнить
0х08 - несовсем понятные значения, два последних байта каким то образом влияют
на скорость, с которой вылетают заспавненые объекты




На последок опишу последний эксперимент. Я решил выяснить - зависит ли как то набор монстрия от номера уровня, т.е. не забито ли это в движке. Для этого я файл первого уровня подменил файлом от шестого. Результат меня приятно удивил - монстры заспавнились как положено, и остальные данные заработали тоже как нужно. Однако не заработали звуки тех монстров, которых на первом уровне не должно быть. Но я уже понял причину - это связано с тем, что ПК вариант игры подгружает набор звуков из текстовых файлов в папке SFX.

Тексты миссий и их скрины остаются от первого уровня.

Пока что на данный момент это всё. Пока буду продолжать дальше разделять границы полей и примерно описывать эффекты, которые они делают.

Автор: -=CHE@TER=- Nov 22 2020, 14:27

Нифига себе сколько. Кстати, на первом скриншоте опечатка - XYX, вместо XYZ.

Прочитал и, пока что, только две мысли:

QUOTE(LexSafonov @ Nov 22 2020, 12:45) *
0х01 - X координата?(Весьма странно описаны координаты, максимум 255? fixed числа?)
0х01 - Y координата?(Тем не менее монстра они двигают)
Может оно тут как в Minecraft? Когда уровень делится на условные кубы и в данном месте задаются координаты куба, в середине которого будет создаваться монстр или предмет.

QUOTE(LexSafonov @ Nov 22 2020, 12:45) *
0х01 - кол-во, которое выдаёт пикап, при поднятии
0х01 - какой то множитель для предыдущего значения
А это случайно не одно значение 0x02 (word, 2 байта) в LITTLE-ENDIAN?

Автор: LexSafonov Nov 22 2020, 15:52

По поводу опечатки, да.... Я заметил это в самый последний момент. Ладно, суть то не больно меняет)))

По поводу координат, скорее всего да, система очень похожая, т.к. один элемент - это 1 блок, он же чётко рисуется на автокарте. Скорее всего в координатах указываются X и Y "блока", а остальные координаты "дорассчитываются" в процессе. Но это пока чисто предположения.

Далее по поводу значений пикапов. Нет, это два разных значения, т.к. я пробовал менять их. Не ясна логика работы "множителя". Я смотрел пару пикапов - пикап пистолетной обоймы и пикап патронов для дробовика. У пикапа пистолетных патронов такие значения 0F 01, что значит - 15 1(15 патронов), а у пикапа патронов дробовика там 0A 01 - 10 1(10 патронов). Если читать в по 2 байта, то больно большое значение получается.... Возможно множитель работает как то от уровня сложности.

Ещё одна проблема в том, что я очень туго соображаю по рисованию графики в С++. Даже если найду все поля, то для написания простенького редактора карт, надо будет применять что то из 3д.... У меня на простой декомпрессор спрайтов чуть ли не неделя ушла)

Автор: -=CHE@TER=- Nov 22 2020, 16:51

QUOTE(LexSafonov @ Nov 22 2020, 15:52) *
Ещё одна проблема в том, что я очень туго соображаю по рисованию графики в С++. Даже если найду все поля, то для написания простенького редактора карт, надо будет применять что то из 3д.... У меня на простой декомпрессор спрайтов чуть ли не неделя ушла)
Вот, нашёл - слегка поправленные мной исходные коды простенького 3D-лабиринта на OpenGL (если найти выход, то игра начнётся заново). Автор Stan Melax - см. комментарии сверху. Для работы программы необходим в том же каталоге что и программа файл maze.bmp с рисунком 128x128 в 24-х битах - это текстура стен, любой файл туда положи указанного размера и глубины цвета.
Можешь разбираться как работает и переделывать под себя - будет с чего начать.
Обрати внимание, что для сборки исполняемого файла тебе нужно указать линковку библиотек "GLU32.dll" и "OpenGL32.dll" помимо стандартных Windows'ых.

maze.c
CODE
/*
* OpenGL Maze Example
*
* by Stan Melax melax@bioware.com
*
* In this little demo the player navigates through a simple maze
* using the arrow keys. The maze is defined by a 2D array where
* each element in the array indicates solid or empty space. This
* program wraps polygon (quad) walls around the solid space and
* disallows the player to navigate into solid space during the demo.
* Note that all the walls are limited to being 90 degrees to each
* other - there are no "angled" features. The purpose of this
* sample program is to show a beginning 3D game programmer some
* things they can do.
*
* One other cool thing that this program does is that it constucts
* a single quad strip to draw all the walls by doing a recursive
* depth first search on the maze array data.
*
* Permission to execute this program, or look at the code is only
* granted to those who do not like to sue other people :-)
* Some of the window setup code was stolen from a simple Cosmo example.
* OpenGL is a trademark of SGI.
*/

#include <assert.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#include <math.h>

#define IDM_APPLICATION_EXIT (101)
#define IDM_APPLICATION_TEXTURE (102)
#define IDM_APPLICATION_BANK (103)
HMENU hMENU;
HWND hWND;
HDC hDC;
HGLRC hGLRC;
int enablebank=0;
int enabletexture=0;

#define MAZE_HEIGHT (16)
#define MAZE_WIDTH (16)

// unfortunately due to the way the polygon walls are generated there
// are restrictions on what the wall/maze data can look like. See below.
char mazedata[MAZE_HEIGHT][MAZE_WIDTH + 1] = {
"****************",
"* * *",
"* * *** * * *",
"* ** * ** * * *",
"* * * *",
"********** *** *",
"* * *",
"* ***** *** ****",
"* * * * *",
"* ******* *",
"* * * * * *",
"* ***** **** * *",
"* * * *",
"** ** **** *** *",
"* * * * *",
"************* **",
};

int wall(int x, int y) {
// true if the region at x,y is solid space that
// should be surrounded by other solid space or polygon walls
return((x >= 0) && (y >= 0) && (x < MAZE_WIDTH) && (y < MAZE_HEIGHT) && (mazedata[y][x] != ' '));
}

/*
* The next group of routines implements the depth-first search
* that is used to wrap a quad strip around all the solid regions of the
* maze. Note this enforces certain topological restrictions on the
* maze data itself. There cant be any loops, clusters, or floating pieces
* existing by themselves. The solid nodes must be a tree (graph theory speak).
*
*/
int onopen(int x, int y) {
// returns whether node x,y is on the depth-first search open list
assert(wall(x, y));
return(mazedata[y][x] == '*');
}

void closeit(int x, int y) {
// puts node x,y on the closed list
assert(wall(x, y));
assert(onopen(x, y));
mazedata[y][x] = 'X';
}

int neighbor(int x, int y, int w, int *nx, int *ny) {
// if x,y has a neighbor in direction w then returns true
switch (w) {
case 0: *nx = x-1; *ny=y; break;
case 1: *nx = x; *ny=y+1; break;
case 2: *nx = x+1; *ny=y; break;
case 3: *nx = x; *ny=y-1; break;
default: assert(0);
}
return(wall(*nx, *ny));
}

int diagnol(int x, int y, int w, int *nx, int *ny) {
switch(w) {
case 0: *nx = x-1; *ny=y-1; break;
case 1: *nx = x-1; *ny=y+1; break;
case 2: *nx = x+1; *ny=y+1; break;
case 3: *nx = x+1; *ny=y-1; break;
default: assert(0);
}
return(wall(*nx, *ny));
}

// normal vectors for each wall direction
float nrml[4][3] = {
{ -1.0f, 0.0f,0.0f},
{ 0.0f, 1.0f,0.0f},
{ 1.0f, 0.0f,0.0f},
{ 0.0f,-1.0f,0.0f},
};

// default color for each wall direction
float clr[4][3] = {
{ 1.0f, 0.0f,0.0f},
{ 0.0f, 1.0f,0.0f},
{ 0.0f, 0.0f,1.0f},
{ 1.0f, 1.0f,0.0f},
};

static float texcoordX = 0.0f;

int dw(int x, int y, int p) {
// the recursive draw wall routine that extends the quad strip
int w = p; // w is the current wall direction being considered
closeit(x, y);
do {
int x2, y2;
if (neighbor(x, y, w, &x2, &y2)) {
if (onopen(x2, y2)) {
dw(x2,y2,(w+3)%4);
} else {
assert(((w + 1) % 4) == p); // or a loop or cluster exists
return(1);
}
} else {
float fx;
float fy;
if (diagnol(x, y, w, &x2, &y2) && onopen(x2, y2)) {
dw(x2,y2,(w+2)%4);
}
glNormal3fv(nrml[w]); // useful iff using lighting
glColor3fv(clr[w]);
texcoordX = (texcoordX < 0.5) ? 1.0f : 0.0f;
fx = (float) x + (((w == 1) || (w == 2)) ? 1.0f : 0.0f);
fy = (float) y + (((w == 0) || (w == 1)) ? 1.0f : 0.0f);
glTexCoord2f(texcoordX, 0.0f); // useful iff using textures
glVertex3f(fx, fy, 0.0f);
glTexCoord2f(texcoordX, 1.0f);
glVertex3f(fx, fy, 1.0f);
}
w++;
w %= 4;
} while (w!=p);
return(1);
}

int drawwalls(void) {
int dl;
glNewList(dl = glGenLists(1), GL_COMPILE);
glBegin(GL_QUAD_STRIP);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 1.0f);
dw(0, 0, 0);
glEnd();
glEndList();
return(dl);
}

//-----------------------------------------------

int drawtop(void) {
// draws the top and the bottom of the maze
// which is useful for overhead views
// The display list created here is only used for the intro
// spinning bit. No optimizations such as using quad strips
// or combining adjacent polygons are done here.
int x, y, dl;
glNewList(dl = glGenLists(1), GL_COMPILE);
glPushAttrib(GL_TEXTURE_BIT | GL_LIGHTING_BIT);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
for (y = 0; y < MAZE_HEIGHT; y++) {
for (x = 0; x < MAZE_WIDTH; x++) {
if (wall(x, y)) {
// bottomside:
glVertex3f(x+0.0f ,y+0.0f ,0.0f );
glVertex3f(x+0.0f ,y+1.0f ,0.0f );
glVertex3f(x+1.0f ,y+1.0f ,0.0f );
glVertex3f(x+1.0f ,y+0.0f ,0.0f );
// topside:
glVertex3f(x+0.0f ,y+0.0f ,1.0f );
glVertex3f(x+1.0f ,y+0.0f ,1.0f );
glVertex3f(x+1.0f ,y+1.0f ,1.0f );
glVertex3f(x+0.0f ,y+1.0f ,1.0f );
}
}
}
glEnd();
glPopAttrib();
glEndList();
return(dl);
}

#define STARTING_POINT_X (1.5f)
#define STARTING_POINT_Y (1.5f)
#define STARTING_HEADING (90.0f)

float player_x = STARTING_POINT_X;
float player_y = STARTING_POINT_Y;
float player_h = STARTING_HEADING; // player's heading
float player_s = 0.0f; // forward speed of the player
float player_m = 1.0f; // speed multiplier of the player
float player_t = 0.0f; // player's turning (change in heading)
float player_b = 0.0f; // viewpoint bank (roll)
int walllist = 0;
int mazelist = 0;

void spinmaze(HDC hDC);
void entermaze(HDC hDC);
void navmaze(HDC hDC);
void (*idlefunc)(HDC hDC) = NULL;

void spinmaze(HDC hDC) {
// spin the maze around on 2 axis
static float spin = 720.0f;
spin -= 5.0f; // change this line to be time dependant!
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glPushMatrix();
glTranslatef(0.0f, 0.0f, -20.0f);
glRotatef(spin, 0.0f, 1.0f, 1.0f);
glTranslatef(-MAZE_WIDTH / 2.0f, -MAZE_HEIGHT / 2.0f, 0.0f);
glCallList(walllist);
glCallList(mazelist);
glPopMatrix();
SwapBuffers(hDC);
if (spin <= 0.0f) {
spin = 720.0f;
idlefunc = entermaze;
player_x = STARTING_POINT_X;
player_y = STARTING_POINT_Y;
player_h = STARTING_HEADING;
}
}

void entermaze(HDC hDC) {
static float p=0.0f;
p += 0.02f; // hmmm, should be time dependant
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glPushMatrix();
glRotatef(-90.0f*p, 1.0f, 0.0f, 0.0f);
glRotatef(player_h*p, 0.0f, 0.0f, 1.0f);
glTranslatef(
-(player_x*p + MAZE_WIDTH/2.0f*(1 - p)),
-(player_y*p + MAZE_HEIGHT/2.0f*(1 - p)),
-(0.5f*p + 20.0f*(1-p))
);
glCallList(walllist);
glCallList(mazelist);
glPopMatrix();
SwapBuffers(hDC);
if (p >= 1.0f) {
p = 0.0f;
idlefunc = navmaze;
}
}

int forward(float px, float py, float bf) {
// this routine does wall collision detection
// the inputs to this routine are:
// - the desired location
// - the minimum distance to wall allowed
// changes:
// - the player's x and y coordinates
// returns:
// - whether a wall caused change in target position
// This is really easy with these walls that lie only on axes.
// If the player collides into a wall at an angle he/she will
// still slide along the wall - I hate programs where you stick
// to the polygon and cant move until you back away from it.
// This collision detection isn't perfect - if you're precise you
// can jump through at a corner - but its tough.
int x = ((int)player_x);
int y = ((int)player_y);
int h = 0; // number of walls hit
if ((px > x + 1.0f - bf) && wall(x + 1, y)) {
px = (float)(x) + 1.0f - bf;
h++;
}
if ((py > y + 1.0f - bf) && wall(x, y + 1)) {
py = (float)(y) + 1.0f - bf;
h++;
}
if ((px < x + bf) && wall(x - 1, y)) {
px = (float)(x) + bf;
h++;
}
if ((py < y + bf) && wall(x, y - 1)) {
py = (float)(y) + bf;
h++;
}
player_x = px;
player_y = py;
return(h);
}

void navmaze(HDC hDC) {
// navigate through the maze and render it from the
// players point of view.
// Ideally updates to heading and position should be time dependant
forward(
player_x + player_m*player_s*(float)sin(player_h*M_PI/180),
player_y + player_m*player_s*(float)cos(player_h*M_PI/180),
0.2f
);
player_h += player_t;
player_b = 3*player_b/4 + player_t/4;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glPushMatrix();
glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
if (enablebank) {
glRotatef(-player_b, 0.0f, 1.0f, 0.0f); // add roll to viewpoint
}
glRotatef(player_h, 0.0f, 0.0f, 1.0f);
glTranslatef(-player_x, -player_y, -0.5f);
glCallList(walllist); // no need to draw the top since we're in the maze now
glPopMatrix();
SwapBuffers(hDC);
if ((player_x > MAZE_WIDTH) || (player_y > MAZE_HEIGHT)) {
// start over
idlefunc = spinmaze;
}
}

#define IMG_SIZE 128
static BOOL bTexLoaded = FALSE;
void readtexture(void) {
BYTE *image;
DWORD dw;
HANDLE fl;
if (bTexLoaded) { return; }
// the bitmap must be a 24 bit bmp file thats 128x128
// I think I mixed up red and blue componants - oh well :-)
fl = CreateFile("maze.bmp", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (fl != INVALID_HANDLE_VALUE) {
SetFilePointer(fl, 54, NULL, FILE_BEGIN);
dw = IMG_SIZE*IMG_SIZE*3;
image = (void *) LocalAlloc(LMEM_FIXED, dw);
if (image) {
ReadFile(fl, image, dw, &dw, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, 3, IMG_SIZE, IMG_SIZE, 0, GL_BGR, GL_UNSIGNED_BYTE, image);
// let texture wrap-around
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// use point sampleing (fastest)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnable(GL_TEXTURE_2D);
LocalFree(image);
bTexLoaded = TRUE;
}
CloseHandle(fl);
}
}

static void SetupDC(HDC hDC) {
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), /* size of this pfd */
1, /* version num */
PFD_DRAW_TO_WINDOW | /* window types */
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA, /* RGBA type */
8, /* 8-bit color depth */
0, 0, 0, 0, 0, 0, /* color bits (ignored) */
0, /* no alpha buffer */
0, /* alpha bits (ignored) */
0, /* no accumulation buffer */
0, 0, 0, 0, /* accum bits (ignored) */
16, /* 16-bit depth buffer */
0, /* no stencil buffer */
0, /* no auxiliary buffers */
PFD_MAIN_PLANE, /* main layer */
0, /* reserved */
0, 0, 0, /* no layer, visible, damage */
/* masks */
};
int SelectedPixelFormat;
BOOL retVal;

// see if the pixel format exists
SelectedPixelFormat = ChoosePixelFormat(hDC, &pfd);
if (SelectedPixelFormat == 0) {
MessageBox(
WindowFromDC(hDC),
"Failed to find acceptable pixel format.",
"OpenGL application error",
MB_ICONERROR | MB_OK
);
ExitProcess(1);
}
// use the pixel format
retVal = SetPixelFormat(hDC, SelectedPixelFormat, &pfd);
if (retVal != TRUE) {
MessageBox(
WindowFromDC(hDC),
"Failed to set pixel format.",
"OpenGL application error",
MB_ICONERROR | MB_OK
);
ExitProcess(1);
}
}

LRESULT APIENTRY WinProc(HWND hWND, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {

case WM_CREATE:
hDC = GetDC(hWND);
SetupDC(hDC);
hGLRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hGLRC);
glEnable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE);
glEnable(GL_CULL_FACE);
glShadeModel(GL_FLAT);
walllist = drawwalls();
mazelist = drawtop();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, 1.0, 0.1, 60.0);
glMatrixMode(GL_MODELVIEW);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glLoadIdentity();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
idlefunc = spinmaze;
return(0);
break;

case WM_DESTROY:
if (IsZoomed(hWND)) {
ShowWindow(hWND, SW_RESTORE);
}
if (hGLRC) {
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hGLRC);
}
ReleaseDC(hWND, hDC);
PostQuitMessage(0);
return(0);
break;

case WM_PAINT:
if (hGLRC) {
// not necessary to do anything here
// since we're constantly drawing to the window anyway
//PAINTSTRUCT ps;
//BeginPaint(hWND, &ps);
//Redraw(hDC);
//EndPaint(hWND, &ps);
return(0);
}
break;

case WM_CONTEXTMENU:
TrackPopupMenu(
hMENU, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
LOWORD(lParam), HIWORD(lParam), 0, hWND, NULL
);
break;

case WM_SIZE:
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
break;

case WM_COMMAND:
switch (LOWORD(wParam)) {

case IDM_APPLICATION_EXIT:
PostQuitMessage(0);
break;

case IDM_APPLICATION_TEXTURE:
enabletexture ^= 1;
((enabletexture) ? glEnable : glDisable)(GL_TEXTURE_2D);
if (enabletexture) {
readtexture();
}
break;

case IDM_APPLICATION_BANK:
enablebank ^= 1;
break;

}
break;

case WM_CHAR:
switch (LOWORD((int)wParam)) {

case VK_ESCAPE:
PostQuitMessage(0);
break;

case 'b':
enablebank ^= 1;
break;

case 't':
enabletexture ^= 1;
((enabletexture) ? glEnable : glDisable)(GL_TEXTURE_2D);
if (enabletexture) {
readtexture();
}
break;
}
break;

case WM_KEYDOWN:
switch (LOWORD((int)wParam)) {

case VK_LEFT:
player_t = -5.0f;
break;

case VK_RIGHT:
player_t = 5.0f;
break;

case VK_UP:
player_s = 0.05f;
break;

case VK_DOWN:
player_s = -0.02f;
break;

case VK_SHIFT:
player_m = 3.0f;
break;

case VK_RETURN:
if (IsZoomed(hWND)) {
ShowWindow(hWND, SW_RESTORE);
} else {
ShowWindow(hWND, SW_MAXIMIZE);
}
break;
}
break;

case WM_KEYUP:
switch (LOWORD((int)wParam)) {

case VK_LEFT:
// the reason for the "if(" is that sometimes
// the player hits the right key down before the
// left key is all the way up.
if (player_t < 0.0f) {
player_t = 0.0f;
}
break;

case VK_RIGHT:
if (player_t > 0.0f) {
player_t = 0.0f;
}
break;

case VK_UP:
player_s = 0.0f;
break;

case VK_DOWN:
player_s = 0.0f;
break;

case VK_SHIFT:
player_m = 1.0f;
break;
}
break;

default:
break;
}
return(DefWindowProc(hWND, message, wParam, lParam));
}

//int APIENTRY WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst, LPSTR lpszCmdLine, int nCmdShow) {
int main(void) {
char *className = "OpenGLWnd";
char *winName = "Maze Example";
WNDCLASS winClass;
MSG msg;
RECT rc;

// Define and register the window class
ZeroMemory(&winClass, sizeof(winClass));
winClass.style = CS_HREDRAW | CS_VREDRAW;
winClass.lpfnWndProc = WinProc;
winClass.hInstance = GetModuleHandle(NULL);//hCurrentInst;
winClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
winClass.hbrBackground = GetStockObject(WHITE_BRUSH);
winClass.lpszClassName = className;
RegisterClass(&winClass);

// create window
hWND = CreateWindow(
className, winName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
0, 0, 320, 240,
NULL, // Parent window's handle
NULL, // Menu handle
winClass.hInstance, // Instance handle
NULL // No additional data
);

// center window
ZeroMemory(&rc, sizeof(rc));
rc.right = 320;
rc.bottom = 240;
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, FALSE);
rc.right -= rc.left;
rc.bottom -= rc.top;
rc.left = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
rc.top = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
MoveWindow(hWND, rc.left, rc.top, rc.right, rc.bottom, FALSE);

// add quit menu item
hMENU = CreatePopupMenu();
AppendMenu(hMENU, MF_STRING, IDM_APPLICATION_EXIT, "Exit");
AppendMenu(hMENU, MF_STRING, IDM_APPLICATION_TEXTURE, "Add/Remove Texture");
AppendMenu(hMENU, MF_STRING, IDM_APPLICATION_BANK, "Add/Remove Banking");

ShowWindow(hWND, SW_SHOWNORMAL);//nCmdShow);
UpdateWindow(hWND);

// DEBUG: use textures by default
SendMessage(hWND, WM_COMMAND, MAKELONG(IDM_APPLICATION_TEXTURE, 0), 0);

msg.wParam = 0;
// process events
while (1) {
// oops, busy wait when no messages or idlefunc - ohwell
if (idlefunc) {
idlefunc(hDC);
// PATCH: do not eat 100% of CPU
Sleep(1);
}
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
if (GetMessage(&msg, NULL, 0, 0) != TRUE) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return(msg.wParam);
}

Автор: LexSafonov May 13 2021, 16:11

Так ребята, всем привет. Долго меня не было. Вообщем сразу по делу буду, без лишних разговоров smile.gif

Решил я доразобрать по сильнее файл с картой. Сначала первый заход был с так называемым "полем", отвечающим за коллизию, а именно "коллижн-блоки".

На данный момент структура одного такого блока. Напомню, 1 блок - это 16 байт описание.

CODE
- формат одного квадрата коллизии(поле с блоками физ. движка)
-------------------------------------------------------------------------------------------------------------------------
0x04 - порядковый номер? пока не совсем ясно как это работает, некоторые номера дают стабильный вылет. Кажись это связано как то с тем, что игра некорректно определяет два одинаковых блока в разных местах.

0x02 - непонятно что это и непонятно для чего оставили. Пока просмотрел 5 разных карт и почему то везде там нули. Вполне вероятно, что там может быть что то по эффектам.

0x01 - значение есть, но не ясен эффект
0x01 - то же самое

0x01 - кажись высота рендеринга потолка блока(черный туман)
0x01 - кажись высота рендеринга пола блока(черный туман)(не точно)

0x01 - высота потолка у блока?(эти значения работают и редактируются в хекс-редакторе)
0x01 - высота пола у блока?

0x01 - непонятное значение
0x01 -хм... блок перестаёт быть неосязаемым, если туда забить простые числа. значение от 55 и ниже блочат игрока. Кажись это какая то высота для блока, только непонятно для чего оно и не совсем ясна
логика работы этой высоты.

0x01 - цвет освещения в блоке? Нашёл вот такие значения:
FF - окрашивает в красный цвет
64 - моргает красный цвет
128 - синий цвет
160 - желтый
192 - серый?
224 - толи оранжевый, толи красный.
от 1 до 10 - в блоке яркий свет
От 11 до 17 - в блоке темно?
18 - ярко красно моргает
20 - полный свет в блоке
22 - оранжево моргает
24 - моргает тёмно-оранжевым
30 - моргает с темного на белый

0x01 - походу байт, который отвечает за действия. если забить 1, то открывает стартовую дверь.
значение:
1 - стартовая дверь
3 - дверь, которая открывается рубильником
9 - конец уровня


Из опыта с высотами блоков на первый взгляд выяснилось, что используется система пол-потолок, как в думе\дюке. Становится понятна логика работы движка. Видимо игра действительно технически поделена на "сетку из блоков" и в качестве координат у объектов принимает позицию блока(длина-строка). Остальное "считается" уже дальше.
Такая система имеет и свои изъяны, а именно, двери и свитчи можно открывать "спиной", ведь движок по факту смотрит чисто нажатие кнопки в самом блоке. Этот прикол уже проверен и неоднократно)

Далее я немного дорасшиффровал заголовок файла:
CODE
- сначала заголовок файла:
размер      содержимое                              комментарий
-------------------------------------------------------------------------------------------------------------------------
0x04        46 4F 52 4D                       FORM
0x04        -                                       размер данных файла в байтах, BIG-ENDIAN
0x04        -                                       количество карт в файле текстом.(обычно нуль, т.к. больше
                                                        одной карты в файлах не встречал)

- дальше идет информация о геометрии карты и её содержании, а именно:
размер      содержимое                              комментарий
-------------------------------------------------------------------------------------------------------------------------
0x04        -                                       Название карты, текст
0x04        -                                       размер данных карты, BIG-ENDIAN(прямой порядок байт)
0х02        -                                       Кол-во вершин?.LITTLE-ENDIAN(обратный порядок байт)
                                                         Формула - значение этих двух байт умножить на 8
                                                                   (6 байт на 3 точки + 2 байта нули)

0x02                                                 Кол-во квадов(прямоугольников).LITTLE-ENDIAN
                                                        (обратный порядок байт). Формула - значение этих 2-х байт
                                                        умножить на 20 (16 байт индексы точкек и 4 байта информация)

0x02             ------------                    Длина "прямоугольника" мини карты(физ. движка)(Little-Endian)
0х02             ------------                    Ширина "прямоугольника" мини карты(физ. движка)(Little-Endian)
                                                       Формула для этих байт = умножить длину на ширину
                                                       и полученое значение умножить на 16
                                                                         16 байт описывают одну ячейку.


0х02         -  Стартовая позиция игрока по осиX (Здесь пишется так же )                  
0х02         -  Стартовая позиция игрока по осиY
0х02         -  Пока не ясно что, изменения приводят к зацикливанию игры на этапе подгрузки карты,
иногда к вылету.

0х02         -                               Поле описания монстров, которые просто спавнятся.
                                                 Формула = кол-во элементов умножить на 20
                                                                        (20 байт на одного монстра)
0х02         -                               Поле описания пикапов, формула = кол-во элементов умножить на 8
0х02         -                               Поле описания "коробок" и подобных объектов(свитчей, бочек)
                                                  Формула = кол-во элементов умножить на 16

0x02        -                                Исправление, походу 2 байта это кол-во всех дверей на карте.
                                                 Формула: значение умножить на 8(8 байт один элемент)
                                                                         Формат двери ниже.

0х02        -                                Пока не ясно за что отвечает.

0x02        -                                Похоже на стартовый угол поворота игрока, игрока на старте карты это
                                                                              значение крутит.

0x06        -                                Не ясно что это, эффект постоянно случайный от изменения.

0x04        -                                       Неизвестное значение, каким то образом влияет
                                                       на монстров на уровне. Если монстры на уровне есть
                                                       и выставить нули, то у монстров отключается рендеринг
                                                       спрайтов, но сами они не пропадают


Теперь по порядку, я нашёл ещё 4 разных значения, а именно координаты игрока X и Y , стартовый угол игрока и поле с кол-вом и описанием дверей на карте.
Формат одной двери:
CODE
-------------------------------------------------------------------------------------------------------------------------
0x01        -                                       координата X
0x01        -                                       координата Y
0х01        -                                       непонятный байт
0х01        -                                       время простоя двери в открытом состоянии
0х01        -                                       тэг двери(для использования в коллижн блоках)
0х01        -                                       непонятный байт
0х01        -                                       угол поворота модели двери относительно карты
0х01        -                                       индекс модели из секции D000\1\2\3...


Пока что я не совсем понял вот такие вещи, а именно:
- Если трёхмерная модель карты имеет координаты, то и её коллизия тоже должна иметь какие то стартовые координаты первого блока.
- Так же я не совсем понял, что за последнее поле имеется в файле карты, после поля описания дверей. Там примерно 4000 байт.... Байты каким то образом там упорядочены, но изменения делают вообще случайные вещи(отключают двери, либо наоборот вешают другие эфффекты на "коллижн-блоки". Не могу понять логику их работы...
- Каким образом дверям задаётся движение и их собственная коллизия(если дверь крутить, то игрок и монстры её коллизию видят).В некоторых картах есть двери, которые, видимо, вообще крутятся чисто вокруг своей оси(вроде 4 уровень в игре, где крео-камеры, сами модули имеют походу двери, которые "открываются" крутясь). В теории если это выяснить, то возможно будет вообще делать двери, которые будут открываться в стороны, а не вверх-низ, как в думе.
Ладно, пойду спать, отпишусь по результатам, если чего выйдет))

Автор: -=CHE@TER=- May 14 2021, 18:11

QUOTE(LexSafonov @ May 13 2021, 16:11) *
- Так же я не совсем понял, что за последнее поле имеется в файле карты, после поля описания дверей. Там примерно 4000 байт.... Байты каким то образом там упорядочены, но изменения делают вообще случайные вещи(отключают двери, либо наоборот вешают другие эфффекты на "коллижн-блоки". Не могу понять логику их работы...
Предположу (точно не знаю), что, возможно, это таблица отсортированная для чего-то, чтобы не упорядочивать данные каждый раз при запуске. В смысле, что по этой таблице, возможно, игра как-то получает доступ уже к остальным данным до неё.