Перейти к содержимому
Zone of Games Forum
KosmoRei

Помогите окончательно разобраться в структуре HD6 + DAT (Rogue Galaxy PS2)

Рекомендованные сообщения

Нужно перепаковывать главные архивы Rogue Galaxy с изменением размеров внутренних файлов. Используется пара HD6 (TOC) и DAT (просто файлы друг за другом).

Вроде, похожий формат в Dragon Quest 8, но общедоступных инструментов нет.

За неимением общедоступного адекватного рабочего репакера, пришлось разбираться самому. Плагин к xpert вообще непонятно, как работает (и работает ли?) (жамкаю rebuild файла, а он создается точно таким же, каким и был).

Взял IDA pro, посмотрел в нем на логику работы плагина (извлекать файлы он умеет, по крайней мере). Многое что понял по структуре HD6, однако самое важное до сих пор неясно.

Наверное, япошкам действительно доплачивают за примененные извращения.

 

Ладно, начнем… Файл HD6 состоит из трех блоков.

  1. Блок с уникальными кусочками названий файлов и папок… Типа так они их пожали, чтобы сэкономить место на схожих названиях.
  2. Блок с информацией о том, как собрать название файла из кусочков
  3. Блок с 8-ми байтовыми дескрипторами хранящихся в DAT файлов. Сколько файлов, столько и дескрипторов.

Дескрипторы третьего блока состоят трех частей (это я понял из IDA)

2 байта — ну… вероятно… это… пи-и-и-и-и-и-и, в общем, они определяют как адрес начала цепочки, из которой собирать имя файла во втором блоке, так и влияют на адрес файла в DAT.

3 байта — через хитро закрученную жопу определяют адрес начала файла в DAT.

3 байта — через нехитро закрученную жопу определяют размер файла в DAT.

//--

Итак… 3-мя байтами можно максимум представить число 16 777 215, а DAT больше этого размера. Соответственно, чтобы представить адреса начал файлов, нужно извращаться…

Первые два байта дескриптора постоянно возрастают (от 00 00 до ~FF FF), как только они достигли ~FF FF, они обнуляются, а в коде начинают работать костыли. Там имеется счетчик, сколько раз был перескок от ~FF FF к 00 00.

//--

Пока счетчик перескоков равен 0, адрес файла в DAT вычисляется ТОЛЬКО так:

ulong temp_file_start_addr = (ulong)(16 * ЧИСЛО, СОБРАННОЕ ИЗ ТРЕХ БАЙТОВ, А ЧЕТВЕРТЫЙ ЗАДАЕТСЯ, КАК 00) / 8

А адрес цепочки кусочков названия файла во втором блоке вычисляется ТОЛЬКО так:

uint temp_file_name_pieces_start_addr = ЧИСЛО, СОБРАННОЕ ТОЛЬКО ИЗ ПЕРВЫХ ДВУХ БАЙТ (ushort) + адрес начала второго блока (вот это поворот!!! используются все биты первых двух байт)

//--

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

temp_file_start_addr -= "количество перескоков << 9";
temp_file_name_pieces_start_addr = "количество перескоков << 16" + ЧИСЛО, СОБРАННОЕ ТОЛЬКО ИЗ ПЕРВЫХ ДВУХ БАЙТ (ushort) + адрес начала второго блока

//--

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

В общем, кто чем может, помогите… Как перепаковать архив с изменением размеров хранимых файлов?..

//--

Я перепрогал на шарп то, что увидел в IDA, результат (temp_hd6_dump.txt https://www.dropbox.com/s/ma1lwzpndzbv6j3/temp_hd6_dump.txt?dl=1) аналогичен результату плагина от xpert.

Структура файла temp_hd6_dump.txt такая:

00000000|00016540|0002660A|00000000|chr_lock.chr
00016800|00120250|00026612|00000000|font16.img
00137000|0005A5E0|0002661A|00000000|abi\c001_abi01.pak

адрес начала файла в DAT | размер файла | адрес трех байт в HD6, задающих адрес начала файла в DAT | количество перескоков от ~FF FF к 00 00 | собранное из кусочков имя.

Вот экземпляр файла HD6 https://www.dropbox.com/s/z9hjdzvtvnuqf1n/DATA0_0.HD6?dl=1 Сам гиговый DAT нужен вряд ли, ибо все адреса файлов в нем и их размеры указаны в temp_hd6_dump.txt.

class HD6
    {
        public string FilePath;
        public byte[] Data;

        public uint FirstBlockStartPos { get { return BitConverter.ToUInt32(this.Data, 4); } }
        public uint FirstBlockSize { get { return BitConverter.ToUInt32(this.Data, 8); } }
        public uint FirstBlockPiecesCount { get { return BitConverter.ToUInt32(this.Data, 12); } }
        public uint SecondBlockStartPos { get { return BitConverter.ToUInt32(this.Data, 20); } }
        public uint SecondBlockSize { get { return BitConverter.ToUInt32(this.Data, 24); } }
        public uint FilesCount { get { return BitConverter.ToUInt32(this.Data, 36); } }
        public uint ThirdBlockStartPos { get { return BitConverter.ToUInt32(this.Data, 40); } }
        public uint WholeHD6Size { get { return BitConverter.ToUInt32(this.Data, 48); } }

        public HD6(string file_path)
        {
            this.FilePath = file_path;
            this.Data = File.ReadAllBytes(this.FilePath);

            FileStream hd6_fs = new FileStream(this.FilePath, FileMode.Open, FileAccess.Read);

            uint curr_pos_in_third_block = this.ThirdBlockStartPos;
            byte lowest_byte = 0;
            byte prelowest_byte = 0;
            byte posthighest_byte = 0;
            byte highest_byte = 0;
            uint ushort_from_first_2_bytes = 0;
            uint ushort_from_first_2_bytes_copy = 0;
            uint prev_step_ushort_from_first_2_bytes = 0;
            int two_bytes_overflow_count = 0;
            uint processed_files_count = 0;

            List<string> lines_to_write = new List<string>();

            do
            {
                hd6_fs.Position = curr_pos_in_third_block;
                lowest_byte = (byte)hd6_fs.ReadByte();
                prelowest_byte = (byte)hd6_fs.ReadByte();

                posthighest_byte = 0;
                highest_byte = 0;
                ushort_from_first_2_bytes = BitConverter.ToUInt32(new byte[] { lowest_byte, prelowest_byte, 0, 0 }, 0);
                ushort_from_first_2_bytes_copy = ushort_from_first_2_bytes;

                if (ushort_from_first_2_bytes < prev_step_ushort_from_first_2_bytes)
                    two_bytes_overflow_count++;

                long file_descriptor_start_addr_in_hd6 = hd6_fs.Position;
                lowest_byte = 0;
                prelowest_byte = (byte)hd6_fs.ReadByte();
                posthighest_byte = (byte)hd6_fs.ReadByte();
                highest_byte = (byte)hd6_fs.ReadByte();

                uint test_val_from_3_bytes = BitConverter.ToUInt32(new byte[] { lowest_byte, prelowest_byte, posthighest_byte, highest_byte }, 0);
                ulong temp_file_start_addr_in_dat_as_in_bytes = (ulong)16 * BitConverter.ToUInt32(new byte[] { lowest_byte, prelowest_byte, posthighest_byte, highest_byte }, 0) / 8;
                //ulong temp_file_start_addr_in_dat_as_in_bytes = BitConverter.ToUInt32(new byte[] { lowest_byte, prelowest_byte, posthighest_byte, highest_byte }, 0) >> 7 << 8; //мой эксперимент, результат аналогичен строке выше
                ulong temp_file_start_addr_in_dat = temp_file_start_addr_in_dat_as_in_bytes;

                if (two_bytes_overflow_count > 0)
                {
                  temp_file_start_addr_in_dat -= (ulong)(two_bytes_overflow_count << 9);
                  ushort_from_first_2_bytes_copy = (uint)((two_bytes_overflow_count << 16) + ushort_from_first_2_bytes);
                }
                
                lowest_byte = 0;
                prelowest_byte = (byte)hd6_fs.ReadByte();
                posthighest_byte = (byte)hd6_fs.ReadByte();
                highest_byte = (byte)hd6_fs.ReadByte();

                uint temp_file_size_in_dat = BitConverter.ToUInt32(new byte[] { lowest_byte, prelowest_byte, posthighest_byte, highest_byte }, 0) >> 4;
                long Offset = ushort_from_first_2_bytes_copy + this.SecondBlockStartPos;
                hd6_fs.Position = ushort_from_first_2_bytes_copy + this.SecondBlockStartPos;
//Начиная отсюда я вообще не понимаю, как происходит сборка имени файла, да и не очень-то это важно
                string assembled_file_path = string.Empty;
                int v32 = 0;
                int v27_possible_str_to_read_length = 0;
                int v35 = 0;

                do
                {
                  byte second_block_first_byte = (byte)hd6_fs.ReadByte();
                  byte second_block_second_byte = 0;
                  v27_possible_str_to_read_length = second_block_first_byte;

                  if (second_block_first_byte == 0)
                    break;

                  if (second_block_first_byte >= 128)
                  {
                    second_block_second_byte = (byte)hd6_fs.ReadByte();
                    v27_possible_str_to_read_length = second_block_first_byte + (second_block_second_byte << 8);
                    byte v28 = (byte)(second_block_second_byte % 2);

                    if (v28 == 0)
                    {
                      v35 = ((second_block_second_byte >> 1) + 1) << 8;
                      v27_possible_str_to_read_length = v27_possible_str_to_read_length + 128 - v35;
                    }

                    if (v28 > 0)
                    {
                      v35 = (second_block_second_byte + 1) / 2 << 8;
                      v27_possible_str_to_read_length = v27_possible_str_to_read_length - v35;
                    }

                    Offset++;
                  }
                  
                  hd6_fs.Position = this.FirstBlockStartPos;
                  uint curr_read_str_bytes_count = 0;

                  do
                  {
                    int temp_read_byte = hd6_fs.ReadByte();

                    if (temp_read_byte == 0)
                      curr_read_str_bytes_count++;
                  }
                  while (curr_read_str_bytes_count < v27_possible_str_to_read_length);

                  byte v81 = 0;

                  do
                  {
                    v81 = (byte)hd6_fs.ReadByte();
                    if (v81 == 47)
                      v81 = 92;
                    if (v81 >= 253)
                      v81 = (byte)(v81 - 26);
                    if (v81 >= 227)
                      v81 = (byte)(v81 - 26);
                    if (v81 >= 201)
                      v81 = (byte)(v81 - 26);
                    if (v81 >= 175)
                      v81 = (byte)(v81 - 26);
                    if (v81 >= 149)
                      v81 = (byte)(v81 - 26);
                    if (v81 >= 123)
                      v81 = (byte)(v81 - 26);

                    if (v81 > 0)
                        assembled_file_path += Encoding.ASCII.GetString(new byte[] { v81 });
                  } while (v81 > 0);
                    
                  —v32;
                  hd6_fs.Position = ++Offset;
                } while (v27_possible_str_to_read_length > 0);

                curr_pos_in_third_block += 8;
                prev_step_ushort_from_first_2_bytes = ushort_from_first_2_bytes;
                processed_files_count++;

                string file_line = temp_file_start_addr_in_dat.ToString("X8") + "|" + temp_file_size_in_dat.ToString("X8") + "|" + file_descriptor_start_addr_in_hd6.ToString("X8") + "|" + two_bytes_overflow_count.ToString("X8") + "|" + assembled_file_path;
                lines_to_write.Add(file_line);
              }
              while (processed_files_count < this.FilesCount - 1);

            hd6_fs.Close();
            File.WriteAllLines(Application.StartupPath + "\\temp_hd6_dump.txt", lines_to_write, Encoding.UTF8);
        }
    }

64ee5e21d5dbfd2de69ccb64b04ca6bc.png

3d0d7269c5562fc25f1169c60f7bc4e6.png

//----

На ночь глядя появились идеи, возможно, я со всем разобрался, жаль тему нельзя удалить самому.

Похоже, это мое “Выходит, что положение файла в DAT связано с положением цепочки кусочков названия файла в HD6?” утверждение и это “как вычислять значение первых двух байт в дескрипторе для файла, следующего за этим увеличенным? Ведь они влияют еще и на адрес начала цепочки кусочков его названия.” неверны. Первые два байта в дескрипторах файлов трогать вообще не нужно. Просто делать обратные извращения с делением на 16, умножением на 8 и, невесть зачем, начиная с определенного файла, плюсовать к получившемуся адресу  количество перескоков « 9”

//---

Репакер сделал, тема неактуальна.

Изменено пользователем KosmoRei

Поделиться сообщением


Ссылка на сообщение

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас популярно

  • Продвигаемые темы

  • Последние сообщения

    • Goonswarm Games объявляет о начале работы над официальной русской озвучкой фильма «История POSTAL — Безумное наследие». Goonswarm Games объявляет о начале работы над официальной русской озвучкой фильма «История POSTAL — Безумное наследие». Эта картина рассказывает о пути одной из самых скандальных и влиятельных серий игр в индустрии. Зрителей ждут интервью с создателями, архивные кадры, истории разработки и реакции сообщества. Особое внимание уделено роли фанатов из России и СНГ, которые помогли POSTAL стать культовым проектом. Фильм выйдет на локальных онлайн-сервисах. Точная дата релиза и список площадок будут объявлены дополнительно. «История POSTAL — Безумное наследие» (Going Postal: The Legacy Foretold) — полнометражный документальный фильм, снятый независимой командой энтузиастов. Премьера состоялась в 2024 году. В этом кино собраны уникальные интервью с разработчиками Running With Scissors, журналистами и фанатами, а также редкие архивные материалы, раскрывающие, как POSTAL стал символом игровой контркультуры.
    • Источниками вдохновения автора называют The Legend of Zelda и Crypt of the NecroDancer. Студии Potata Company и HeroCraft PC сообщили о релизе весьма необычного проекта Lord Ambermaze, который сами авторы описывают как «увлекательное приключение на необыкновенном острове, где мир двигается только тогда, когда двигаетесь вы». \ Источниками вдохновения автора называют The Legend of Zelda и Crypt of the NecroDancer.
    • Также были представлены два дополнительных сюжетных набора: Loose Cannon и The Flower & the Flame. Эти наборы позволят игрокам исследовать Сиэтл в роли шерифа Камарильи Бенни Малдуна и примогена Изабеллы Мур, соответственно. Они включены в премиальные издания игры и будут выпущены в 2026 году. Компании Paradox Interactive и White Wolf совместно со студией The Chinese Room объявили, что кланы Ласомбра и Тореадор добавлены в базовую версию игры Vampire: The Masquerade — Bloodlines 2 и будут доступны для всех игроков с момента релиза. Также были представлены два дополнительных сюжетных набора: Loose Cannon и The Flower & the Flame. Эти наборы позволят игрокам исследовать Сиэтл в роли шерифа Камарильи Бенни Малдуна и примогена Изабеллы Мур, соответственно. Они включены в премиальные издания игры и будут выпущены в 2026 году. Цифровые издания Vampire: The Masquerade — Bloodlines 2 включает в себя: Deluxe Edition содержит Santa Monica Memories, косметический набор с предметами декора, вдохновленными оригинальной игрой Vampire: The Masquerade — Bloodlines, для обиталища Фаир. Набор можно будет приобрести отдельно. Premium Edition включает набор Santa Monica Memories и дополнительные сюжетные наборы Loose Cannon и The Flower & the Flame. Loose Cannon расскажет историю о шерифе клана Бруха по имени Бенни, позволив игрокам окунуться в его историю бесконечной погони. Игроки также получат экипировку в стиле Бенни для Фаир. The Flower & the Flame посвящен легендарному пути примогена клана Тореадор Изабеллы к созданию ее легендарного темного magnum opus. Обладатели The Flower & the Flame также получат наряд для Фаир в стиле Изабеллы после релиза набора. В Vampire: The Masquerade — Bloodlines 2 игроки проведут девять ночей в Сиэтле в роли старейшины вампиров Фаир, разбираясь в коварной политике придворной Камарильи и восстанавливая вампирские способности. На этом пути Фаир преследует и направляет голос Фабиена, детектива из клана Малькавиан — его искаженное восприятие реальности одновременно дает ключи к разгадке тайн и затмевает грань между истиной и иллюзией. Игроки могут выбрать один из шести кланов, у каждого из которых свой стиль игры, одежда и дисциплины: мятежный клан Бруха, адептов магии крови из клана Тремер, вершителей правосудия Бану Хаким, харизматичных Вентру, обворожительных представителей клана Тореадор и повелителей теней Ласомбра. Игроки должны быть внимательны к своему окружению, иначе они рискнут нарушить Маскарад — абсолютный закон секретности, который скрывает общество вампиров от человечества. Релиз проекта состоится 21 октября.
    • Ай яй яй Мирослав, как нехорошо. Ты же как фанат соуслайков и считаешь, что должна быть только одна сложность, чтобы страдать и преодолевать. Почему ты его бросил? Ему ведь грустно, что ты фанат соуслайков его бросил? Пожалуйста вернись и скажи ему, что ты был не прав, что бросил его одного там и закончи с этим раз и на всегда
    • @vvildfish проблема в том, что в этих фразах используется SDF шрифт, который не русифицирован. Система fallback шрифтов, если не находит нужных символов подсовывает мультиязычный шрифт на замену, но работает это криво, некоторые символы пропускает и выдаёт квадраты. Чтобы это исправить, надо найти нужный SDF шрифт и заменить его атлас и метрику на русифицированные. Но, чтобы его как-то найти, нужно его хотя бы увидеть в оригинале. Поэтому нужны эти надписи в игре на английском языке. Да и чтобы до них добраться, нужен хотя бы сейв в этом месте, где выводятся эти тексты. Тогда можно будет попробовать визуально идентифицировать этот шрифт, сравнивая его с теми, что есть в игре. По другому я не знаю как это шрифт вычислить.
    • прогресс прям шикарный за пару лет от смешного ,режущего уши до хороший “особо не к чему придраться” ... ленивые фаны видимо 
    • Да, прошёл. Только для этого мне потребовалось 6 часов времени. Я задолбался заучивать все его тайминги. Самый сложный босс в моей жизни. Читал тут, многие просто бросали и не стали его проходить, так как он является опциональный боссом.  Вот тут отчётливо видно, сколько времени я потратил на его прохождение. Клеа, второй по уровню сложности босс в игре, я завалил 17:31. После я пошёл сразу к Симону, и смог его убить только в 23:58. Я был выжат как апельсин в соковыжималке, после его прохождения. Это было конечно нервно и эпично, бодрила только крутая музыкальная композиция на боссе, которая каждый раз играла. 
    • шёл 2 час а квест в канализации всё не начинался )
  • Изменения статусов

    • Дмитрий Соснов  »  Tirniel

      Привет! ты разбираешься в компьютерном железе, сможешь помочь с советом по апгрейду старого компа?
      · 1 ответ
    • SHAMAH

      Куда вход на сайт убрали и ЗАЧЕМ? Хотел файл скачать, там только медленная загрузка и “зарегистрируйтесь”. Все. Пришлось вручную страницу входа прописывать.
      · 0 ответов
    • Nosferatu  »  behar

      Добрый вечер.
      Подскажите пожалуйста, у вас не осталось случайно исходников для фикса на широкоформатные мониторы для игры Vampire The Masquerade Redemption?
      Если да, то не могли бы вы ими поделиться, а если нет, то прошу прощенья что побеспокоил.
      Заранее спасибо.
      · 0 ответов
    • AlcoKolyic  »  makc_ar

      Здраствуйте! Извините, а можно попросить ссылку на место где можно взять перевод (патч или образ игры с переводом) El Shaddai: Ascension of the Metatron для ps3, пожалуйста? А то в теме к этой игре у меня не получилось найти работающие ссылки… Первая ведет в группу в которой удалены большинство постов, а пост с этой игрой ведет на сайт https://psnext.ru который сейчас не имеет отношения к видеоиграм. 
      · 0 ответов
    • oleg72  »  Boor

      https://www.skidrowcodex.net/fate-reawakened-goldberg/
      · 0 ответов
  • Лучшие авторы


Zone of Games © 2003–2025 | Реклама на сайте.

×