Graphics3D в Mathematica
Graphics3D в Mathematica

В предыдущих сериях

В прошлом посте я расписал то, как я нашел в экзешнике LHX.exe сначала всего лишь один байт, значение которого искал — и как я был этим горд. Ну и еще про то, как я изящно почти разобрался со структурой байт, которые окружают строки с названием моделек в игре.

Вот она, эта структура (еще раз):

  1. какой‑то 1 байт

  2. 1 байт, содержащий количество точек лоу‑поли

  3. ссылка на адрес старта списка лоу‑поли точек

  4. непонятная куча каких‑то байтов номер 1

  5. 1 байт, содержащий количество точек хай‑поли

  6. ссылка на адрес старта списка хай‑поли точек

  7. непонятная куча каких‑то байтов номер 2

  8. null‑terminated строка с названием объекта

  9. список лоу‑поли точек

  10. список хай‑поли точек

И еще я написал про то, что не все было радужно, и я столкнулся с несколькими проблемами при попытке полностью понять эту самую структуру.

Первая проблема за раз

Начать я решил с кучи странных байтов номер 1, вполне ожидаемо ожидая, что это вторая из двух (точки и полигоны) составных частей описания модели — список полигонов. Вроде бы тут ей самое место — название есть, точки есть, почему бы уже и полигоны не описать тут же?

А как вообще описать полигоны, имея явный список точек? Очевидно, упоминая эти точки. Упоминая либо:

  • по значению (то есть, именно значению координаты) — что бессмысленно, ведь у нас уже есть список этих значений,

  • по ссылке — и самой дешевой и простой ссылкой будет просто номер точки в списке точек.

Номер вряд ли будет отрицательным, так что это беззнаковое число. Вряд ли точек в модели будет больше 256 — не те времена, не те бюджеты полигонов. Да ведь и в самой струтуре размер списка точек (что эквивалентно максимальному номеру места в списке) указывается тоже одним байтом. Значит, ссылка на трех‑координатную точку — это беззнаковый байт, указывающий на ее номер в списке из максимум 256 точек.

Держа вышесказанное в голове, я скопировал непонятную кучу байтов номер 1 из сегмента, описывающего модель зенитки SA-8 (это которая 9К33, но игру сделали американцы), и представил в Mathematica как список байт — для наглядности и анализа.

И там подозрительно часто начало встречаться значение 255. Явно, что это не номер точки — не тот размер списка точек. Это или разделитель, или еще что‑то, но это явно намекает на какую‑то структуру. Ну или по крайней мере хотя бы на что‑то не про точки. При этом последовательности невысоких значений типа «1, 2, 5, 6» (я же искал номера точек) также встречались.

Кусок кучи байт с выделенными 255 и некоторыми подчеркнутыми последовательностями, похожими на списки точек
Кусок кучи байт с выделенными 255 и некоторыми подчеркнутыми последовательностями, похожими на списки точек

Отбив в тексте энтером несколько последовательностей, начинающихся с 255, я получил двоякую картину. Вроде какая‑то структура проявляется — нолик этот, всегда стоящий за 255. И за ноликом цифра редко меняется… Но порой были выплески, которые ломали всю картину. Возможно, 255 — это не начало структуры?

Но при этом за ноликом почти всегда стояла 4, а за ней — последовательность невысоких величин. Предположив, что эти величины — это все же номера точек, а это последовательность — это список этих номеров, я заметил, что 4 — это то количество предполагаемых номеров, за которым всегда стоит значение 16. И если перед списком номеров стояла не 4 — закономерность сохранялась — список сравнительно невысоких значений этой длины, а потом — 16. Местами логика ломалась — если за 255 стоял не нолик.

За 255 - почти всегда нолик, за ноликом - почти всегда 4, за 4 - всегда последовательности невысоких величин.
За 255 - почти всегда нолик, за ноликом - почти всегда 4, за 4 - всегда последовательности невысоких величин.

Тогда я предположил, что можно начать структуру не 255, а с 16, которая всегда стояла в конце списка точек. Картинка не сильно поменялась.

Картинка немного улучшилась, но не намного.
Картинка немного улучшилась, но не намного.

Но, выискивая в тексте глазами 16-ки, чтобы отбить последовательности, я заметил, что 16 всегда стоит за 4 байта до 255. Предположив, что само значение «16» — не совсем уж константа, а вот ее позиция — да, я отбил последовательности так, чтоб 255 шла пятой по счету с начала записи. Структура более‑менее (но не полностью) сохранилась, к 16 в начале добавились варианты — 8 и 1. Опираясь на визуальную структуру, я добил рассматриваемый кусок до чего‑то более‑менее хоть как‑то объяснимого (хоть и не всегда понятного).

Картинка проясняется еще немножко
Картинка проясняется еще немножко

Видите последовательность со множеством нулей? Это множество заставило меня вспомнить про Ida, про XREF, про то, что возле этого XREF указано количество точек, предположить, что количество полигонов тоже может быть указано — и так и оказалось. Число «17» на 5-й строке сверху на последней картинке = количеству записей с 16, 8 или 0 в начале - а именно их я и подозревал в описании полигонов. Так я нашел счетчик полигонов.

Двойка, которая стоит за 17, вроде показывает, сколько записей с 1 в начале, но позднее оказалось, что это просто совпадение. Что же тогда описывают записи с числом 1 в начале? А там нету счетчика списка вершин, а самих номеров вершин всегда два. Что всегда (потому что нет счетчика) опирается только на 2 вершины? Правильно, линия. Значит, структура с единицей в начале описывает линию. И, значит, 1 — это номер элемента «линия». А 16, 8, 0 — это все же что‑то, связанное с полигонами — ведь количество вершин там переменное от раза к разу.

Но возвращаясь к линии — зачем ей внутри ещё 4 байта данных? Например, «182, 85, 15, 255» у первой линии с последней картинки — она же вроде полностью определена?

Если посмотреть на эту картинку — то видно, что у структур, начинающихся на «3», упоминаются пары байт. Их всегда четное количество. И эти пары байт встречаются собственно у элементов геометрии — как, например, значение «182, 85». Может, это номер элемента и структура с тройкой на них зачем‑то ссылается? Причем этот номер сквозной для всех моделек — иначе зачем там аж 2 байта? Отметив этот факт в Kaitai, я присмотрелся к оставшимся двум цифрам у первой линии — «15, 255».

255, как я уже сказал, упоминался с очень завидным постоянством и постоянно лез в глаза. И это было очень похоже на неизменную константу, такой столб посреди поля. И я бы так и решил, но червячок «кругом все в счетчиках и номерках, а тут целый байт всегда везде одинаковый — к чему бы это?» все же грыз меня.

Неразобранным также осталось значение 15. Сравнив его со значением в других элементах, мне ничего в голову не пришло, так что я благополучно эту позицию в структуре пометил как неопределенную.

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

Например, как это так процессор 8088 так бодро рисует столько таких идеальных сфер в реальном времени и не полыхает как сверхновая при этом?

Черные сферы (видные в основном как точки, потому что они далеко) дают мозгу какую-то пространственную опору (я изобразил ее красными линиями), помогая понять скорость и направление полета по пустыне, которая в 1990-м году изображалась тупо желтой плоскостью. Так вот каждое это черное пятнышко - это сфера. И их много.
Черные сферы (видные в основном как точки, потому что они далеко) дают мозгу какую-то пространственную опору (я изобразил ее красными линиями), помогая понять скорость и направление полета по пустыне, которая в 1990-м году изображалась тупо желтой плоскостью. Так вот каждое это черное пятнышко - это сфера. И их много.
Все те же сферы ориентации + голова ракетчика. Оцените степень их детализации, особенно в сравнении с руками, ногами и, собственно, всего лишь "цилиндрической" ( а не полностью круглой) ПЗРК (по лору - это Стрела-2) у ракетчика. Причем, такое приближение к голове - ну очень редкая и нестандартная ситуация, но при этом голова идеально круглая. В отличие от руки, у которая могла бы быть прямоугольной, но даже тут зажали одну точку и поэтому даже рукав нормальный не сделали.
Все те же сферы ориентации + голова ракетчика. Оцените степень их детализации, особенно в сравнении с руками, ногами и, собственно, всего лишь "цилиндрической" ( а не полностью круглой) ПЗРК (по лору - это Стрела-2) у ракетчика. Причем, такое приближение к голове - ну очень редкая и нестандартная ситуация, но при этом голова идеально круглая. В отличие от руки, у которая могла бы быть прямоугольной, но даже тут зажали одну точку и поэтому даже рукав нормальный не сделали.

Так вот — модель в формате, используемом EA в игре LHX, содержит далеко не только полигоны, которые просто честные многоугольники, видимые только с одной стороны. Еще она содержит двусторонние полигоны (очевидная оптимизация объема данных для тех же крыльев самолета, которые широкие, но практически плоские), и полигоны с обратным порядком обхода точек (наверное, для экономии времени дизайнера при копипасте, чтоб полигон был тот же, но нормаль в противоположную сторону шла), и даже (зачем‑то) скрытые полигоны!

Но даже на этом список не кончается. Ещё есть, как уже известно, линии (самостоятельные или принадлежащие полигону — потому что они всегда лежат на полигоне и никогда не висят в воздухе), есть просто тупо точки и, собственно, есть те самые сферы. И вот у сферы, как и у линии, тоже всего 2 значения без всяких счетчиков — это номер точки где лежит центр сферы, а также ее радиус (в аж 2 байта размером, то есть можно нарисовать сферу в 256 раз больше любой модельки, и я рисовал, и это стремно). Так что это не геометрический примитив, а команда. По которой система просто рисует на честном трехмерном рендере закрашенный круг заданного радиуса. Ларчик открывался просто.

То, что казалось сквозным номером, оказалось абсолютно неуникальным значением, и в некоторых модельках повторялось у нескольких элементов.

А еще я разобрался что такое 255. Почти случайно. Я заметил, что у полигонов, которые являются лопастями, в этом месте стоит значение ниже, чем у других полигонов модельки вертолета. В чем же разница между лопастями и остальными элементами? Да вот же:

Полупрозрачные лопасти
Полупрозрачные лопасти

Это оказалось значение прозрачности. И вот благодаря тому, что в игре практически все — непрозрачное, я и смог уцепиться за это самое лезущее в глаза значение 255 и размотать этот клубок. Зная, что 255 — это прозрачность, последовательность «15, 255» (где на месте 15 — это вообще максимальное встретившееся за все время анализа значение) стала очевидной. 15 — это индекс цвета. Про цвет мне очень долго не приходило в голову просто потому, что в детстве я играл не в ту игру, которую я показывал на картинках выше.

Я играл в такую:

LHX на мониторе CGA. Тут больше паттернов, чем цветов.
LHX на мониторе CGA. Тут больше паттернов, чем цветов.

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

Так или иначе, я разобрался практически со всей геометрией. Осталась парочка таинственных участков, но у меня получилось их отсечь от уже понятных. Финальное (для меня) описание геометрии — под катом.

Kaitai-описание структуры "полигонов"

Наблюдательный глаз увидит, что явно доступен только блок с элементами модельки низкой детализации. А блок с моделькой "высокой" детализации - нет. Это потому, что я не нашел ни ссылки, ни счетчика, который явно сказал бы - "этот блок здесь". Так что пришлось его опознавать по косвенным признакам среди других chunks.

А полигоны в кавычках - потому что там не только полигоны.

meta:
  id: lhx_elements
  file-extension: lhx_elements
  endian: le
seq:
  - id: low_model_chunk
    type: low_model_connection
  - id: chunks
    type: chunk
    repeat: eos

types:
  chunk:
    seq:
      - id: lead_byte
        type: u1
      - id: data
        type:
          switch-on: lead_byte
          cases:
            3   : group
            7   : strange_garb_2
            0   : alias
            16  : alias
            24  : alias
            _   : med_model_connection

  low_model_connection:
    seq:
      - id: num_of_elements
        type: u1
      - id: num_of_points
        type: u1
      - id: points_address_prefix
        doc: usually 00 00 00 00
        #contents: [0x00, 0x00, 0x00, 0x00]
        type: u4
      - id: pointcloud_address
        doc:  if zeroes - points are in the separate file
        type: address
      - id: strange1
        type: u2
      - id: num_of_polygons
        type: u1
      - id: strange2
        type: u1
      - id: elements
        type: element
        repeat: expr
        repeat-expr: num_of_elements   

  med_model_connection:
    seq:
      - id: num_of_points
        type: u1
      - id: points_address_prefix
        doc: usually 00 00 00 00
        #contents: [0x00, 0x00, 0x00, 0x00]
        type: u4
      - id: pointcloud_address
        doc:  if zeroes - points are in the separate file
        type: address
      - id: strange1
        type: u2
      - id: num_of_polygons
        type: u1
      - id: strange2
        type: u1
      - id: elements
        type: element
        repeat: expr
        repeat-expr: _parent.lead_byte   
        
  
  address:
    seq:
      - id: segment
        type: u2
      - id: offset
        type: u2

  element:
    seq:
      - id: element_type
        type: u1
        enum: type_of_element
      - id: some_plane_element_id_1
        type: u2
      - id: element_color_code
        type: u1
      - id: opaqueness
        doc: usually 255
        type: u1
      - id: geometry
        type:
          switch-on: element_type
          cases: 
            'type_of_element::separate_line'  : line
            'type_of_element::on_polygon_line': line
            'type_of_element::double_sided_polygon': polygon
            'type_of_element::hidden_polygon': polygon
            'type_of_element::single_sided_polygon': polygon
            'type_of_element::reversed_normals_polygon': polygon
            'type_of_element::point': point
            'type_of_element::sphere': sphere   
        
  polygon:
    seq:
      - id: strange_3
        type: u1
      - id: num_of_points
        type: u1
      - id: points
        type: u1
        repeat: expr
        repeat-expr: num_of_points
        
  line:
    seq:
      - id: points
        type: u1
        repeat: expr
        repeat-expr: 2
        
  point:
    seq:
      - id: point_num_of_point
        type: u1
  
  sphere:
    seq:
      - id: radius
        type: u2
      - id: point_of_center
        type: u1

  group:
    seq:
      - id: num_of_grouped_ids
        type: u1
      - id: grouped_id
        type: u2
        repeat: expr
        repeat-expr: num_of_grouped_ids

  alias:
    seq:
      - id: real_id
        type: u2
      - id: alias_id_1
        type: u2
      - id: alias_id_2
        type: u2

  strange_garb_1:
    seq:
      - id: two_zeroes
        type: u2
      - id: ids
        type: u1
        repeat: expr
        repeat-expr: 5
        
  strange_garb_2:
    seq:
      - id: num_of_ids
        type: u1
      - id: ids
        type: u2
        repeat: expr
        repeat-expr: num_of_ids
      - id: zeroes
        type: u1
        repeat: expr
        repeat-expr: 6
      - id: strange_numbers
        type: u1
        repeat: expr
        repeat-expr: 3

enums:
  type_of_element:
    0x00: double_sided_polygon
    0x01: separate_line
    0x02: point
    0x03: sphere
    0x08: hidden_polygon
    0x10: single_sided_polygon
    0x11: on_polygon_line
    0x18: reversed_normals_polygon

Результат анализа информации о геометрии, пример применения.
Результат анализа информации о геометрии, пример применения.

Так или иначе, списки точек есть, списки элементов (это все же не просто описание полигонов) для них есть и более‑менее понятны — я все ближе к цели!

Это я в математике накрутил визуализатор для проверок гипотез. Отображается лоу‑поли моделька SA-8 (ниже — референс из игры) — колес нет, ракетницы обозначены просто линиями (на них нарисованные мной красные стрелочки показывают, это не глюк визуализатора). А треугольник, находящийся прямо под фиолетовым прямоугольником сверху и опирающийся на упомянутые линии, в игре не виден — это тот самый невидимый полигон.
Это я в математике накрутил визуализатор для проверок гипотез. Отображается лоу‑поли моделька SA-8 (ниже — референс из игры) — колес нет, ракетницы обозначены просто линиями (на них нарисованные мной красные стрелочки показывают, это не глюк визуализатора). А треугольник, находящийся прямо под фиолетовым прямоугольником сверху и опирающийся на упомянутые линии, в игре не виден — это тот самый невидимый полигон.
Это я в математикенакрутил визуализатор для проверок гипотез. Отображается лоу‑поли моделька SA-8 (ниже — референс из игры) — колес нет, ракетницы обозначены просто линиями (на них красные стрелочки показывают, это не глюк визуализатора). А треугольник, находящийся прямо под фиолетовым прямоугольником сверху и опирающийся на упомянутые линии, в игре не виден — он тот самый невидимый полигон.
Лоу-поли модельки SA-8 в игре

Осталось всего ничего — достать наружу собственно модельки, т. е. конкретные данные по точкам и элементам. Так как это все вшито в экзешнике, а я сознательно саботировал любое обучение DOS, то оставалось одно — выковыривание списков точек и элементов руками. Какие‑то попытки автоматизации я делал, но они были весьма условны.

Вторая проблема за раз

Как я уже говорил в прошлой статье, в процессе ковыряния в экзешнике я не раз сталкивался с тем, что у некоторых объектов указан список элементов, но нет списка точек (т. е. описание модельки обрывается на названии объекта, и после него сразу начинается список элементов следующей модельки). Но откуда‑то же они должны браться! Логика подсказывала, что они или во внешних файлах ресурсов или все же почему‑то архивированы внутри экзешника. Второй вариант был сознательно отброшен — помним про нежелание лезть в архитектуру. И я начал изучать внешние файлы — те, которые были распакованы утилиткой, написанной на Nim.

И пожалел, что не сделал этого раньше. Оказалось, для каждого объекта из игры есть ресурсный файлик формата PNT. Я их, естественно, видел ранее, но я думал, что это paint — типа как красить, текстуры какие‑то. Даже искал графический редактор тех времен под DOS, который поддерживает такое расширение.

Много файлов .PNT
Много файлов .PNT

Но ведь в описании элемента уже указывается цвет (и даже прозрачность, да)! Так что это явно не текстуры. И, скорее всего, это не paint, а points, мда.

И так и оказалось. Каждый файл pnt — это именно список точек (или два, если у объекта есть лоу-поли и мид-поли модель) объекта. Для каждого объекта в игре, в том числе и тех, для которых список точек уже приведен в экзешнике. Тут, признаться, я немного не понял стратегии EA - зачем? Вроде ж как должны свободное место бороться… Но потом решил, что это решение было предвестником наступающих (лет через 30) игр размером в десятки и сотни гигабайт. 

Так или иначе, pnt-файлы содержали описание точек в немного в другом формате:

  • каждая координата длиной уже не в байт, а в два. В kaitai это решается заменой в одном месте строки «s1» на «s2».

  • после списка точек модели идет какой‑то набор байт… В общем, это был не первый и далеко не последний раз, когда я разобрался с тем, что эти байты есть и сколько их, но не разобрался, зачем они. У меня была гипотеза про BSP — но ровно так же я мог иметь гипотезу про XYZ или ЭЮЯ. По итогу я этот набор отслеживал и потом скипал. В названии упоминается BSP — но 98% что это не оно.

  • в начале файла есть заголовок, содержащий только адреса блоков с точками и вот этими, как я решил, bsp — элементами.

Kaitai-описание структуры точек (и одновременно файла формата .PNT)

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

meta:
  id: lhx_points
  file-extension: pnt
  endian: le
seq:
  - id: header
    type: header_info
    
    #s1
  - id: dotcloud_low
    type: dotcloud
    size: header.set1_end_offset - header.set1_start_offset
 
    # bsp 1_1
  - id: dotcloud_low_bsp_1
    doc: bsp 1 till the end of file -> s110s200
    if: header.set1_bsp_end_offset == 0 and header.set2_start_offset == 0
    type: u1
    repeat: eos
    
    # bsp 1_2
  - id: dotcloud_low_bsp_2
    doc: bsp 1 between set 1 and addon 1 -> s111s200 s111s210 s111s210
    if: header.set1_bsp_end_offset != 0
    type: u1
    repeat: expr
    repeat-expr: header.set1_bsp_end_offset - header.set1_end_offset
    
    # bsp 1_3
  - id: dotcloud_low_bsp_3
    doc: bsp 1 between s1 and s2 -> s110s210 s110s211
    if: header.set1_bsp_end_offset == 0 and header.set2_start_offset != 0
    type: u1
    repeat: expr
    repeat-expr: header.set2_start_offset - header.set1_end_offset
    
    # a 1_1 == not exist -> s110s200 s110s210 s110s111
    
    # a 1_2
  - id: dotcloud_low_addon_2
    doc: addon 1 till end of file -> s111s200
    if: header.set1_bsp_end_offset != 0 and header.set2_start_offset == 0
    type: u1
    repeat: eos

    # a 1_3
  - id: dotcloud_low_addon_3
    doc: addon between bsp 1 and set 2 -> s111s210 s111s211
    if: header.set1_bsp_end_offset != 0 and header.set2_start_offset != 0
    type: u1
    repeat: expr
    repeat-expr: header.set2_start_offset - header.set1_bsp_end_offset
    
    #s2
  - id: dotcloud_med
    type: dotcloud
    if: header.set2_start_offset != 0
    size: header.set2_end_offset - header.set2_start_offset
    
    # bsp 2_1 == not exist -> s110s200 s111s200
    
    # bsp 2_2
  - id: dotcloud_med_bsp_2
    doc: bsp 2 till end of file -> s110s210 s111s210
    if: header.set2_start_offset != 0 and header.set2_bsp_end_offset == 0
    type: u1
    repeat: eos
    
    # bsp 2_3
  - id: dotcloud_med_bsp_3
    doc: bsp 2 between set 2 and addon 2
    if: header.set2_start_offset != 0 and header.set2_bsp_end_offset != 0
    type: u1
    repeat: expr
    repeat-expr: header.set2_bsp_end_offset - header.set2_end_offset 
    
    # a 2_1 == not exist -> s110s200 s111s200 s110s210 s111s210
  - id: dotcloud_med_addon_2
    doc: addon 2 till end of file -> s110s211 s111s211
    if: header.set2_start_offset != 0 and header.set2_bsp_end_offset != 0
    type: u1
    repeat: eos
    
types:
  header_info:
    seq:
        - id: zerostart
          type: u2
        - id: set1_start_offset
          type: u2
          doc: Number of bytes to skip from the beginning of file.   
        - id: set2_start_offset
          type: u2
          doc: Number of bytes to skip from the beginning of file. Equals zero if there is no set 2 
        - id: set1_end_offset
          type: u2
          doc: Number of bytes to skip from the beginning of file. 
        - id: set2_end_offset
          type: u2
          doc: Number of bytes to skip from the beginning of file. Equals zero if there is no set 2
        - id: set1_bsp_end_offset
          type: u2
        - id: set2_bsp_end_offset
          type: u2  
        - id: strange3
          type: u2  
        - id: strange4
          type: u2  
  dotcloud:
    seq:
        - id: dots
          type: dot
          repeat: eos
  dot:
    seq:
    - id: x
      type: s2
    - id: z
      type: s2
    - id: y
      type: s2

Таким образом, набор точек для каждого объекта я нашел. А наборы элементов для каждого объекта были вшиты в экзешнике. Я их путем тупого гринда выковырял и сохранил. Для каждой модельки получилось 2 файла — .pnt (просто скопированный из игровых файлов) и .bin (вручную скопированный из экзешника дамп с элементами. В нем даже XREF не вырезан).

Формально, цель достигнута!

Звучат фанфары, дофамин растекается по мозгу, солнце светит на 24% ярче. Было — ничего, стало — исходники моделек. Которые лежат вот тут.

Вынесенные уроки

Философия

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

Бекграунд

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

Общие моменты

  1. Если значения данных меняются, но всегда находятся на одинаковых позициях — это структурированный блок данных. Надо постараться формализовать эту структуру — какой длины данные на каких местах стоят. А после этого — разбираться с каждым элементом структуры по очереди. При этом можно оказаться в ситуации, когда:

    1. Вся структура формализована, но непонятно, за что отвечает какие‑то из ее элементов.

    2. Вся структура формализована, но непонятно, за что отвечают все ее элементы.

    3. Структура непонятна совсем, можно только знать ее общий размер.

  2. Если определенный блок данных предполагает вариабельность — то есть если это список/вектор, а не массив, то в данных будет содержаться способ показать размер этого блока. Это может быть:

    1. Счетчик перед списком. Но необязательно вплотную.

    2. Указание смещения относительно какой‑то точки. Точка обычно одна — это начало файла, нулевой элемент в массиве с содержимым файла. Поэтому смещения скорее всего будут также указаны в начале файла - в заголовке.

Заголовки файлов

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

  1. Постараться найти маленькие файлы, плюс в идеале — какой‑нибудь вырожденный случай (типа файла со списком точек, в котором только одна точка).

  2. Брать значения из начала (особенно повторяющиеся) — и отсчитывать расстояния от начала файла. Если совпадет с чем‑нибудь очевидным — то это смещение.

Небольшой пример
Иллюстративные 6 скринов с содержимым файлов точек
Иллюстративные 6 скринов с содержимым файлов точек

Верхняя строчка на всех картинках смотрится более-менее одинаково, особенно первые 4 байта - 00 00 12 00. Только в одном случае (CITY.PNT) там проскакивает 08, но закроем на это глаза. Если предположить, что 0x12(18 в десятичной) - это расстояние до чего-то и посчитать эти 19 байт (zero-based index) с начала файла пальчиком - то видно, что как раз 18 по счету значение - меняется от файла к файлу. А вот 17 и 18 - всегда нули, что еще больше укрепляет нас в этой мысли. 

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

Но все же! Встречаются же “01”, “02”! А больше 02 и нету. А может это не самостоятельные байты? А может, это старшие разряды идущих перед ними байтов? В IBM PC порядок байт - задом наперед же, сначала младшие байты, потом старшие. То есть, 91 02 читается как 02 91 = 0x291. Что явно больше 12 00 = 0x12. То есть, во-первых и предыдущая догадка про смещения подкрепляется, и тип данных, получается, не байт, а слово (которое 2 байта).

Так что надо считать пальчиками и угадывать назначение блоков…

Cliffhanger

Но… как же похвастаться этими модельками? Как посмотреть полгода спустя, когда все нюансы уже забыты? А может, захочется их распечатать на 3D‑принтере? Поэтически выражаясь, я выучил новый язык и понял сказки на нем — но как рассказать их тем, кто этого языка не знает?

Надо переводить!

Комментарии (5)


  1. Yoti
    19.09.2024 10:31
    +2

    Возможно, дублирование данных было вызвано наличием игры на разных платформах (или иным образом разделение по версиям)? Где-то было меньше ресурсов и всё вшивали в исполняшку, а где-то хотели накрутить красоты, но так и не завершили. Или, например, это хвосты от сборки, не почищенные в релизе...


    1. unbalanced Автор
      19.09.2024 10:31

      У меня (уже когда я статьи писал) возникла версия, что в экзешник засовывали то, что чаще встречается - для "хэширования", но потом я тоже склонился к версии с хвостами.


  1. SadOcean
    19.09.2024 10:31

    Впечатляет "хай поли", если в нем, как и в лоу поли максимум 255 вершин)


    1. unbalanced Автор
      19.09.2024 10:31
      +1

      Это их официальный термин)

      Но я по тем же причинам в голове называю это "мид-поли".


      1. SadOcean
        19.09.2024 10:31

        Сложно их в этом упректуть, представляю, каким чудом это смотрелось в свое время.