Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Работа с файлами неизвестных форматов. Анализ структуры, написание редактора.

9.7K
16 февраля 2011 года
Vitamant
228 / / 07.02.2011
Доброго времени суток!

Так получилось, что основы программирования, равно, как и вся теория, прошли мимо меня стороной. И вот столкнулся я с необходимостью разобрать неизвестный формат файла. Понятно, что под расширением DAT может скрываться что угодно. И, вероятно, это архив. Заглянул WinHex'ом (я его впервые вижу, пользоваться не умею) - и впрямь, вначале идут имена файлов "лесенкой" (в текстовом виде).
 
Код:
28 04 00 00 AC 46 00 00 41 62 79 73 73 2E 64 6C 72

Вроде, понятно:
28 04 - идетификатор заголовка файла.
00 00 - разделитель (или часть следующего)
AC 46 - вероятно смещение на начало файла и\или длина
00 00 - очередной разделитель
41 62 79 73 73 2E 64 6C 72 - собственно имя файла, что видно в текстовом виде "Abyss.dlr"
Дальше идет следующий файл:
00 00 00 00 9C 5C 22 00 41 62 79 73 73 32 2E 64 6C 72

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

Абалдеть! Я только что понял, что байты записываются в обратном порядке!.. Тоесть отображаются! И AC 46 - это на самом деле 46 AC и там действительно начинается запись чего-то нового. Вот так начнешь писать на форуме и оно приходит! :)

Но, не суть. Это частный случай. Да и текстовый вид помог. Но все-таки я вовсе не чувствую себя увернным и способным написать анпакер, а тем более пакер подобного архива.
Может быть, посоветуете какой-нибудь материал на тему? Почитать, поизучать. Какие-нибудь советы? Тонкости работы в HEX... Что-нибудь, чтобы научиться читать HEX, аки книгу...

Буду благодарен любой помощи и советам!

---

Да, и буду очень благодарен, если подскажите как с этим работать в реалиях C#. К примеру банально считать список файлов. Имеется ввиду правильно и удобно. А то я сейчас начну заводить List<byte>, искать первый 00, потом последний 00, выставлять позицию в -= 1, записывать циклом по байту в этот список, потом конвертировать в long... Думаю, это малость неправильно... Если можно - крошечный примерчик.
277
16 февраля 2011 года
arrjj
1.7K / / 26.01.2011
типы int или unsigned int это 4 байта, записанные в обратном порядке т.е. число 1 хранится как 01 00 00 00. Выложи побольше какойнить участок файла. Чтоб определить сжат или нет - поищи в dat'е кусок из распаковыного "архива".
9.7K
16 февраля 2011 года
Vitamant
228 / / 07.02.2011
Вот кусочек файла размером 6мб. Теоретически, там весь заголовок и еще 16 файлов, которые, я, вроде, даже ровно обрезал.
http://www.mediafire.com/?b0ygu8s3g6ug89r
Честно говоря, не думаю, что он сжат... А вот этого не понял: 'поищи в dat'е кусок из распаковыного "архива".' Если смотреть на содержимое предполагаемых файлов, то точно не сжат - там даже виден внятный текст. Да и zip ужал его в 6 раз. Сомнительно.
Но, в любом случае, мне главное научиться с этим работать самостоятельно. Так что выслушаю все советы, прочитаю предложенные книги, с удовольствием поковыряюсь в небольших примерчиках. Главное, чтобы потом я мог разобраться в незнакомом формате самостоятельно. Собственно этот вот dat - только начало. Дальше идут жуткие файлы с расширением dlr, которые тоже придется разобрать и вот там уже придется как-то учиться это анализировать... Там может быть графика, а может и какие-то тех.данные. В общем, очень хочу научиться анализировать непонятно что и писать редакторы для работы с этим. Буду рад любой помощи.

P.S. За разъяснение по поводу int - спасибо! Понял! Запомню!.. А short, long (int16\64)?

---

Вот еще интересно: после имени файла идут \0. Но не 1, а произвольное количество. Почему? То 4, то 3, то 1... Или это для выравнивания для кратности 4?..

---

Все, осознал, там просто длина файла фиксированная - 12 байт... И почему умные мысли приходят только после того, как задашь вопрос на форуме?..

---

А первые 4 байта это, часом не количество ли файлов?.. Мда... вот тебе и неизвестный формат...
4
17 февраля 2011 года
mike
3.7K / / 01.10.2002
Похоже что это не архив, а образ. Это не TAR и не образ дисков FAT12/FAT16/FAT32. Можно попробовать найти какую нибудь выдералку файлов. Там есть WAV файлы. Они есть в списке, кроме того, их можно найти внутри файла, по ключевому слову "RIFF". Можно посмотреть смещение и длину, потом сопоставить с данными в списке файлов - есть в начале. Так можно будет понять структуру списка файлов и выдернуть все файлы.

Может быть проще, если знать форматы других файлов - dli, dlr, dlv. Но я понятие не имею что это такое :) Погугли.
9.7K
17 февраля 2011 года
Vitamant
228 / / 07.02.2011
Дельный совет, но, боюсь, гугл промолчит. Вероятнеее всего это эксплюзивный формат одной ныне покойной game-студии. Впрочем, попытка не пытка. Я уже осознал, как там все устроено. Оказывается, все не так страшно, как казалось на первый взгляд. Сейчас нарисую анпакер и возьмусь за эти самый dli, dir, div. Так что I'll be back в самое ближайшее время. :)
9.7K
17 февраля 2011 года
Vitamant
228 / / 07.02.2011
Обалдеть! Оно отработало! :D Даже Wav'ки играются! Мой первый разобранный файл... :)

Взгляните, пожалуйста, на софтинку, и скажите - в чем я не прав. Наверняка, что-нибудь можно было реализовать поизящнее!
Код:
class Program
    {
        static void Main(string[] args)
        {
            FileInfo file = new FileInfo(args[0]);
            byte[] buff = new byte[17];

            using (FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
            {
                stream.Read(buff, 0, 4);
                UInt32 count = BitConverter.ToUInt32(buff, 0) - 1;
                Console.WriteLine(String.Format("Распаковка файла: {0}", file.Name));
                Console.WriteLine(String.Format("Количество файлов: {0}", count));
                Console.WriteLine("-------------------");

                FileHeader[] files = new FileHeader[count];
                for (UInt32 index = 0; index <= count; index++)
                {
                    stream.Read(buff, 0, 17);
                    FileHeader fs = new FileHeader(BitConverter.ToUInt32(buff, 0), Encoding.GetEncoding(1251).GetString(buff, 4, 12).TrimEnd(new char[] { '\0' }));
                    if (index != count)
                    {
                        files[index] = fs;
                    }
                    if (index != 0)
                    {
                        files[index - 1].lenght = fs.shift - files[index - 1].shift;
                    }
                }

                DirectoryInfo dir = new DirectoryInfo(file.FullName + "_");
                if (!dir.Exists) dir.Create();
               
                foreach (FileHeader s in files)
                {
                    Console.WriteLine(String.Format("|Файл: {0} | Смещение:  {1:x}| Длина:  {2}", s.name, s.shift, s.lenght));
                    stream.Position = s.shift;
                    byte[] b = new byte[s.lenght];
                    stream.Read(b, 0, (int)s.lenght);
                    using (FileStream output = new FileStream(String.Format(@"{0}\{1}", dir.FullName, s.name), FileMode.Create, FileAccess.Write))
                    {
                        output.Write(b, 0, (int)s.lenght);
                    }
                }
            }
        }
    }

    public class FileHeader
    {
        public UInt32 shift = 0;
        public UInt32 lenght = 0;
        public string name = "";

        public FileHeader(UInt32 shift, string name)
        {
            this.shift = shift;
            this.name = name;
        }
    }
7
17 февраля 2011 года
@pixo $oft
3.4K / / 20.09.2006
Фигасе производительность!:eek:

Не увидь я прогресса в работе,я бы написал,что самому разбираться в неизвестных форматах–задача,как минимум,крайне сложная…Однако,увидя за такое короткое время написанный парсер,я взмолчу:)
9.7K
17 февраля 2011 года
Vitamant
228 / / 07.02.2011
Польщен. :o
Но тут все было до примитивизма просто. :) Вот дальше будет весело! :)
P.S. А если есть подобный опыт - поделись! Пригодится любая информация. :)
---
О! А заголовок то я могу считать единым массивом, ибо его длина = 4(количество записей) + 17*количество записей!
297
17 февраля 2011 года
koodeer
1.2K / / 02.05.2009
Цитата: Vitamant
Взгляните, пожалуйста, на софтинку, и скажите - в чем я не прав. Наверняка, что-нибудь можно было реализовать поизящнее!
 
Код:
Console.WriteLine(String.Format("Распаковка файла: {0}", file.Name));


Методы Write и WriteLine в качестве первого параметра могут принимать строку формата, поэтому можно записать чуть короче, без string.Format:

 
Код:
Console.WriteLine("Распаковка файла: {0}", file.Name);

Но это несущественно.


Цитата: Vitamant
А если есть подобный опыт - поделись! Пригодится любая информация. :)


Вот здесь показан разбор базы данных. Может пригодится.
http://forum.codenet.ru/threads/61199-%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82-%D0%91%D0%94


В целом - весьма похвально! Так держать!

9
17 февраля 2011 года
Lerkin
3.0K / / 25.03.2003
Не, ну молодец, чоужтам. Только боюсь, что для разбора файлов типа .dli .dlr .dlx (из этого архива) - одного визуального анализа будет мало.

З.Ы. Че это за игруха? Музон какой-то бешенный :)
19K
17 февраля 2011 года
Alegun
269 / / 10.09.2010
Может не надо столько героических усилий прикладывать, лучше приберечь силы на что-то другое :)Попробуйте распаковать с помощью DragonUnpacker (он заточен для раздербанивания игровых потрохов).
9.7K
17 февраля 2011 года
Vitamant
228 / / 07.02.2011
Облагородил софтину. Нарисовал упаковщик. Вроде, делает все правильно (но нет проверки на длину файлов [не может быть больше 12 байт]). Выкладываю весь код:
1. Дабы у таких же новичков, как я, был пример работы с простенькими форматами файлов.
(Сам архив: http://www.mediafire.com/?bf7vyw3bzz4vcmz [40мб])
2. Дабы гуру подсказали - что еще можно улучшить. В частности очень не нравится сшивка файлов в конце упаковки. Наверняка ведь есть способ и по-лучше и по-быстрее (впрочем, и этот довольно быстрый).
Код:
namespace BoYRepacker
{
    class Program
    {
        static void Main(string[] args)
        {
            Mode mode = Mode.Unpack; // режим работы - упаковка или распаковка
            FileInfo file = new FileInfo("Z:\\z.tmp"); // DAT-архив
            DirectoryInfo dir = new DirectoryInfo("Z:\\"); // папка с файлами

            // Проверка количества аргументов
            if (args.Length < 3) { onDone(2, args.Length); }

            // Парсинг аргументов
            switch (args[0])
            {
                case "p":
                    mode = Mode.Pack;
                    file = new FileInfo(args[args.Length - 1]);
                    dir = new DirectoryInfo(args[args.Length - 2]);
                    break;
                case "u":
                    mode = Mode.Unpack;
                    file = new FileInfo(args[args.Length - 2]);
                    dir = new DirectoryInfo(args[args.Length - 1]);
                    break;
                default: onDone(3, args[0]); break;
            }

            // Проверка существования входного объекта
            if ((mode == Mode.Pack) && !dir.Exists)
                onDone(4, dir.FullName);
            if ((mode == Mode.Unpack) && !file.Exists)
                onDone(4, file.FullName);

            // Проверка существования выходного объекта
            if (!(args.Contains<string>("/y") || args.Contains<string>("/Y")))
            {
                bool outputExist = false;
                if ((mode == Mode.Pack) && file.Exists)
                    outputExist = true;
                if ((mode == Mode.Unpack) && dir.Exists)
                    outputExist = true;
                if (outputExist)
                {
                    Console.WriteLine("Выходной объект уже существует! Вы уверены, что хотите перезаписать его?");
                    Console.WriteLine("Перезаписать {0}? [Y - да, N - нет]", (mode == Mode.Pack ? file.FullName : dir.FullName));
                    while (true)
                    {
                        int c = Console.Read();
                        if ((c == -1) || ((char)c == 'n') || ((char)c == 'N')) { onDone(1); }
                        else if (((char)c == 'y') || ((char)c == 'Y')) { break; }
                    }
                }
            }

            // Создаем несуществующие папки
            if (!file.Directory.Exists)
                file.Directory.Create();
            if (!dir.Exists)
                dir.Create();

            // Запуск операции
            if (mode == Mode.Pack) { pack(file, dir); } else { unpack(file, dir); }
        }

        /// <summary>
        /// Упаковка папки
        /// </summary>
        /// <param name="file">Выходной DAT-файл</param>
        /// <param name="dir">Входная папка</param>
        static void pack(FileInfo file, DirectoryInfo dir)
        {
            // Пишем-с
            using (FileStream stream = new FileStream(file.FullName, FileMode.Create, FileAccess.Write))
            {
                FileInfo[] files = dir.GetFiles("*", SearchOption.TopDirectoryOnly);
                byte[] header = new byte[4 + 17 * (files.Length + 1)];

                Console.WriteLine("Упаковка файла:     {0}", file.Name);
                Console.WriteLine("Количество файлов:   {0}", files.Length);
                Console.WriteLine("-----------------------------------");

                // Записываем количество файлов (+1 пустышка)
                stream.Write(BitConverter.GetBytes(files.Length + 1), 0, 4);

                // Пишем заголовок
                UInt32 shift = (UInt32)(4 + 17 * (files.Length + 1));                
                for (int i = 0; i < files.Length; i++)
                {
                    shift += (UInt32)(i > 0 ? files[i - 1].Length : 0);
                    byte[] name = new byte[13];
                    Encoding.GetEncoding(1251).GetBytes(files.Name).CopyTo(name, 0);
                    stream.Write(BitConverter.GetBytes(shift), 0, 4);
                    stream.Write(name, 0, 13);
                    Console.WriteLine("| Файл: {0}  | Смещение: {1:x}   |", files.Name, shift);
                }
                // Не забываем несуществующий объект, для вычисления длины последнего файла
                shift += (UInt32)(files[files.Length - 1].Length);
                stream.Write(BitConverter.GetBytes(shift), 0, 4);
                stream.Write(new byte[13], 0, 13);
                Console.WriteLine("-----------------------------------");

                // Последовательно записываем файлы один за другим
                for (int i = 0; i < files.Length; i++)
                {
                    using (FileStream f = new FileStream(files.FullName, FileMode.Open, FileAccess.Read))
                    {
                        Console.WriteLine("| Файл: {0}  | Смещение: {1:x}   | Размер: {2} |", files.Name, stream.Position, (int)files.Length);
                        byte[] buff = new byte[files.Length];
                        f.Read(buff, 0, (int)files.Length);
                        stream.Write(buff, 0, (int)files.Length);
                    }
                }
            }
        }
       
        /// <summary>
        /// Распаковка архива
        /// </summary>
        /// <param name="file">DAT-файл</param>
        /// <param name="dir">Выходная папка</param>
        static void unpack(FileInfo file, DirectoryInfo dir)
        {
            // Читаем-с
            using (FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
            {
                // Считываем количество файлов
                byte[] header = new byte[4];
                stream.Read(header, 0, 4);
                UInt32 count = BitConverter.ToUInt32(header, 0);

                Console.WriteLine("Распаковка файла: {0}", file.Name);
                Console.WriteLine("Количество файлов:   {0}", count - 1);
                Console.WriteLine("-----------------------------------");

                // Считываем заголовок
                header = new byte[count * 17];
                stream.Read(header, 0, (int)(count * 17));
               
                byte[] buff;
                for (UInt32 index = 0; index < count - 1; index++)
                {
                    UInt32 shift = BitConverter.ToUInt32(header, (int)(index * 17));
                    UInt32 length = BitConverter.ToUInt32(header, (int)((index + 1) * 17)) - shift;
                    string name = Encoding.GetEncoding(1251).GetString(header, (int)(4 + index * 17), 12);

                    Console.WriteLine("| Файл: {0} | Смещение: {1:x}    | Длина: {2}   |", name, shift, length);

                    buff = new byte[length];
                    stream.Position = shift;
                    stream.Read(buff, 0, (int)length);
                   
                    using (FileStream output = new FileStream(String.Format(@"{0}\{1}", dir.FullName, name.TrimEnd(new char[] { '\0' })), FileMode.Create, FileAccess.Write))
                    {
                        output.Write(buff, 0, (int)length);
                    }
                }
            }
        }

        // Вывод сообщения о завершении работы и выход из программы
        static void onDone(int exitCode, object param = null)
        {
            switch (exitCode)
            {
                case 0:
                    Console.WriteLine("Операция успешно завершена.");
                    break;
                case 1:
                    Console.WriteLine("Операция прервана пользователем.");
                    break;
                case 2:
                    Console.WriteLine("Неверное число параметров: {0}" + Environment.NewLine, param);
                    goto default;
                case 3:
                    Console.WriteLine("Неизвестный ключ запуска: {0}" + Environment.NewLine, param);
                    goto default;
                case 4:
                    Console.WriteLine("Входной объект не существует: {0}" + Environment.NewLine, param);
                    goto default;
                default:
                    Console.WriteLine("Распаковка или упаковка игровых архивов Battle of the Youstrass.");
                    Console.WriteLine("2011 (c) Vitamant [vitamant@gmail.com]" + Environment.NewLine);
                    Console.WriteLine("{0} [p | u] [/Y] источник результат" + Environment.NewLine, Process.GetCurrentProcess().ProcessName);
                    Console.WriteLine("  p      Упаковка входной папки в выходной DAT-архив.");
                    Console.WriteLine("  u      Распаковка входного DAT-архива в выходную папку.");
                    Console.WriteLine("  /Y     Подтверждение перезаписи выходного объекта.");
                    Console.WriteLine("  источник   Распаковываемый DAT-архив или упаковываемая папка.");
                    Console.WriteLine("  результат Распакованная папка или упакованный DAT-архив." + Environment.NewLine);
                    break;
            }
            Environment.Exit(exitCode);
        }
    }

    /// <summary>
    /// Режим работы - распаковка или упаковка
    /// </summary>
    public enum Mode
    {
        Pack, Unpack
    }
}


А я приступаю к разбору того, что находится внутри этого образа-архива. :)
9.7K
17 февраля 2011 года
Vitamant
228 / / 07.02.2011
Lerkin
Battle of the Youstrass :)

kooder
Спасибо большое! Наверняка поможет!

Alegun
Познание ради познания - мое любимое развлечение. :) А DU тоже кто-то писал. И DU позволяет распаковать архив (что я и сам уже сделал), но не редактировать внутренние ресурсы. Так что впереди меня ждет неравный бой: "чайник" против "dlv, dli, dlr, sn" :)
9.7K
20 февраля 2011 года
Vitamant
228 / / 07.02.2011
Следующим пал ниц предо мною формат fdb
http://www.mediafire.com/?pdxbrl0y6mnj2m2

Файл с именем Stage1.fdb просто не мог не относиться к одной из игровых локаций. Первой, если точнее. Заглянув внутрь, обнаружил чудную картинку:

Ровные колонки "00" не давали и шанса взглянуть это, как на какую-нибудь разумную управляющую последовательность, равно, как и большое количество повторяющихся ненулевых байт. Не долго думая, забил все первым попавшимся на глаза (D1 00 D1 00 D1 00 D1 00...), упаковал, запустил игру. И довольно быстро заметил изменения. Ими оказались условия проходимости клеток игровой карты. Осталось составить список байт и сопоставить с ними те или иные условия проходимости. Все просто до отвращения. Правда, отдельно эту штуку использовать не получилось, так как размеры карт меняются, а внутри файла нет ну абсолютно никаких намеков на количество клеток. Двумерный массив представлен одномерной последовательностью и совершенно неюзабилен в отсутствии самих клеток.

К сожалению, здесь опять помог визуальный анализ, поэтому я прошу людей знающих иные методы анализа файлов помочь советом или умной книжкой, ибо об другой я зубы уже обломал. Им оказался сопутствующий файл *.sn:
http://www.mediafire.com/?l1l15176fxxkr2c
Никаких признаков константных структур навели на неприятную мысль - файл пожат. Также косвенным признаком оказалось то, что файл ужатый в zip стал весить всего на 18% меньше. Банальной подменой Stage1 на Stage51, выяснил, что это аж целая карта притом, по всей видимости, еще и с текстовыми диалогами, помимо самих ID тайлов. И вот тут я зашел в тупик. С чего начать? За что хватиться? Можно ли визуально определить алгоритм сжатия (если он там есть, хотя должен быть - ведь текстов невооруженным глазом не видно)? Или же поочереди перебирать наиболее популярные, особенно в 2000 (дата выхода игры) алгоритмы, ища нужный? LZ77/78, LZW, Deflate?.. Тогда существуют ли готовые решения для их распаковки? (чтобы в программе пришлось реализовывать только один, а не все, ибо нужный без сомнения окажется последним). Или сразу хватать в зубы ассемблер и смотреть - как там все устроено и как игра с этим взаимодействует? Тогда буду признателен за любую информацию о нем, ибо прошел он мимо меня и я лишь знаю, что это язык с очень простым синтаксисом, и очень сложный "для чайника" во всем остальном. :)

---

Следующим отонсительно адекватным форматом оказался dli.
http://www.mediafire.com/?jujeduucrwhi9cq

Еще не разобран, но не думаю, что окажется чем-то сверх сложным. Коротенький заголовок и большое количество повторяющихся байт, плавно перетекающих одних в другие, навели на мысли о графике. Путем подмены одного файлика другим, выяснил, что это действительно так. Просто картинки и есть мнение, что каждый пиксель кодируется отдельным байтом. Игрушка работает в режиме 256 цветов, так что едва ли здесь скрываются что-то более сложное. Попытаюсь нарисовать конвертет 2BMP. В заголовке, видимо, пишется палитра и\или размеры изображения. Еще есть небольшой хвост. Возможно палитра указана как раз в нем.

...sn по-прежнему держится крепким орешком. Нужна помощь...
10
20 февраля 2011 года
Freeman
3.2K / / 06.03.2004
Цитата: Vitamant
поочереди перебирать наиболее популярные, особенно в 2000 (дата выхода игры) алгоритмы, ища нужный? LZ77/78, LZW, Deflate?.. Тогда существуют ли готовые решения для их распаковки?


Большинство (если не все) форматы Z-семейства умеет распаковывать 7-Zip -- как из командной строки, так и на уровне библиотеки (предполагаю). Нужно только убедиться, что 7z.exe полный, не 7za.exe или 7zm.exe (забыл) -- суть урезанная версия. Полная версия требует 7z.dll для работы.

9.7K
20 февраля 2011 года
Vitamant
228 / / 07.02.2011
Все здорово, но 7z ведь предполагает, что это архив, верно? А там вовсе не архив, а просто ужатый по определенному алгоритму набор данных. Следовательно:
---------------------------
7-zip
---------------------------
Не удалось открыть файл 'Stage49.sn' как архив
---------------------------
ОК
---------------------------
Ну и из командной строки соответственно тоже самое.
9.7K
08 марта 2011 года
Vitamant
228 / / 07.02.2011
Сдались все три формата: *.pal, *.dli, *.sn

pal, как не сложно догадаться, оказалась палитрой в 256 цветов. Достаточно было только взглянуть на ровные колонке цифр, разделенных 00, чтобы опознать записанные в RGB цвета + неиспользуемый альфа-канал. Размер файла 1056 байт. 1056 - 256*4 = 32 => длина заголовка. В начале стоит более чем понятное "DLPalette". За ним нули, а вот начиная с 16го байта - странное. Признаться, над двумя идущими подряд integer'ами я думал долго - просто не мог никуда приладить. А вот когда у моей картинки, всунутой в игру, поплыли цвета, пришло озарение. Посчитал количество идущих вначале "пустых цветов" (00 00 00 00) и вуаля - оно сошлось с первым int'ом. Вычил из 256 - получил второе. Почему-то все используемые цвета "прижаты" к концу файла, а неиспользуемые из отведенных 256 наоборот - лежат вначале. Возможно, это следствие работы оптимизиратора палитры (об этом ниже).
Собственно, с палитрой на этом и все. Ничего сложного. Едем дальше...

dli, наличие файла палитры рядом с говорящими названиями Title и WorldMap, являлось едва ли не 90% гарантией, что это изображения. Для теста поменял одно на другое, игру запустил - ага! Все верно.
Далее приступил к разбору. Помня опыт работы с DAT-файлом, первым делом обратил внимание на заголовок, в котором явственно читались два целых числа: 80 02 00 00 и E0 01 00 00. Перевел - все просто. 640х480. Размеры изображения.
Дальше был ступор: 00 00 00 00 01 01 A0 00
Непонятно что, непонятно зачем. Не имея альтернатив - плюнул и пока забыл.
Смотрю дальше.
Тут, признаться, на помощь пришли криворукие русские пираты, которые поленились сделать человеческую запаковку файлов и записали их всех без сжатия, так что в отличии от непонятной кучи байт в английских версиях файлов, в русских мне предстала чудная картинка:
 
Код:
C1A0C1A0C1A0C1A0C1A0C1A0C1A0C1A0
C1ACC1ACC1A5C1A0C1A0C1A0C1A0C1A5
C1A0C1A2C1A2C1B3C1BEC1ACC1A5C1A5
C1ACC1ACC1ACC1ACC1A0C1A2C1A2C1A2

И так весь файл. C1 - неизменно. За ней - переменная. В конце файла одинокий 0C.
Поскольку для формирования изображения, как минимум, нужно знать цвет каждого его пикселя (или их группы), картинка стала предельно ясной. Что-то, за ним номер цвета из палитры. Что-то, еще один цвет. Никаких управляющих конструкций - полоска пикселей проходит до границы изображения - переходит на новый ряд. Все предельно просто. Забил всю "рабочую" область файла последователньостью C1A0 - в игре получил однородную заливку. Все просто.
Взглянул в английскую версию, а там все выглядело совсем по-другому:
 
Код:
FFA0FFA0C2A0C2ACA5C4A0A5A0C2A2B3

Сейчас это кажется очевидным, но тогда повергло в некоторый шок. Впрочем, уже через час экспериментов с изменением этих байт, и припоминанием вычитанного в этой замечательной статье:
http://shedevr.org.ru/forum/viewtopic.php?t=3590
Было вычислено, что C1 - это 1 пиксель, C2 - 2 пикселя... и т.д. до FF. А за ними идет цвет, которым указанное количество пикселей будет заполнено. Все предельно просто. Также эксперименты указали, что C0 заливает всю строку выбранным цветом до конца. А ниже нее - делает с картинкой странное.
Впрочем, главное я для себя определил. Впереди идущий байт определяет длину последователньости пикселей со сдвигом на 192 (С0). И может соответственно быть не больше FF (63 подряд идущих пикселя).
Написал конвертер и долго созерцал кашу непонятно чего.
Стал более подробно изучать имеющиеся данные. Обнаружил, что в английской версии почти не встерчается C1, в то время, как одиночные пиксели игра как-то закрашивает. К тому же, были найдены управляющие байты, значение которых меньше 192. Поэкспериментировав с ними, я обнаружил, что от этого меняется цвет! Притом всегда - одного пикселя. Эксперименты подтвердили смутные догадки - обрезав файл и забив его произвольной последовательностью байт, которые видел в файле, я получил строчку из разноцветных пикселей. Взглянул в палитру по соответствующим номерам, перевел цвета из HEX в десятичную RGB и вбил в Paint'е. Цвета сошлись. Из этого был сделан вывод - байты ниже 192 обозначают пиксели отдельного цвета.
Поправил конвертер - сдохло. Встретились пиксели, которых нет в палитре! И тут же взгляд уткнулся в заголовок, где маячил байт с тем же номером. Опять же эксперименты и вуаля - предпоследний байт заголовка определяет прозрачный цвет, который может даже не существовать в палитре - это пиксель не отрисовывается, а просто пропускается.
Наконец, все было готово. Две картинки с pal-файлами успешно переведены в gif'ы (хотя сейчас я знаю, что родным для разработчиков был формат pcx, но уже поздно). Но были еще сотни изображений, в которых никакими палитрами и не пахло!
Рыл я долго. Один добрый человек меткими пинками направлял меня в нужную сторону (хотя я старательно норовил свернуть и искал Единую Палитру для всех изображений, пробовал даже подцепить стандартную Win256; после ковырял exe-файлы игры...). Все без толку.
Уже отчаявшись, решился на страшное: взвел виртуальную машину с WinXP. Запустил на ней игру. Сделал скриншот экрана, на котором маячило интересующее изображение. В любимом Paint'е взял пипеткой образец цвета, перевел из десятичного rgb в HEX, добавил в конце 00 и... запустил поиск по игровому архиву... и к своему удивлению нашел, что искал!
Посмотрел по смещению и ощутил, как у меня вспотели зубы - судя по названию это был тот самый файл изображения.
Открыл, пролистал в конец... и обнаружил, что после 0C идет еще коротенький блок в 256 байт, содержащий 64 цвета. :)
Файлы не имеющие прикладной палитры, я даже не удосужился пролистать до конца.
Поправил конвертер, проверил - все сдохло. В файле цвета указаны с номерами аля 144, которых в принципе не может быть, при условии, что у меня их всего 64.
Было потрачено еще полдня на эксперименты. Палитра в конце файла забивалась красным, синим, зеленым и изменения проходили. Принялся изучать заголовок и вот тут страшное и раскрылось - последний байт заголовка отнимается от номера цвета!
Зачем? Вероятно, по той же причине, по которой палитра пишется от конца к началу, а не наоборот. Оптимизация для тех самых одиночных цветов ниже 192.
На этом разбор формата был закончен.

sn
Самое страшное, и одновременно самое скучное. Просто потому, что решение было найдено добрым человеком за меня. За что я ему, естетсвенно, благодарен, но никакого морального удовлетворения от написания софтинки к этому формату, испытать не удалось.
Это текстовые файлы, зашифрованные по алгоритму DEC с 8ми байтными независимыми блоками и 8ми байтным же единым ключом. Ключ он мне сказал, дальше оставалось лишь копнуть MSDN и еще немного постучаться головой об стену, в поисках свойства Padding. Файлы были успешно дешифрованы и зашифрованы обратно.

От игры остались dlv, dlr, dlx, dlb файлы.
dlr - анимация
dlx - эффекты
dlb - вероятно бинарные базы
dlv - пока неизвестно

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

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

Ну и наконец, озвученный еще в первом посте вопрос насчет ассёмблера и изучения памяти процессов остается открытым. Кто что может подсказать из серии "для чайников на русском" по ним - буду премного благодарен.:)
9.7K
10 июня 2011 года
Vitamant
228 / / 07.02.2011
Тэкс, новая проблема об которую второй день ломаю зубы. Следующий формат - dlv преподнес неприятный сюрприз. От dli он особо ничем не отличается, за исключением описания заголовка. Он короткий - всего 4 байта. Третий и четвертый управляют цветами. Первый - отвечает за ширину. Второй - за высоту.

Ширина расчитывается просто:
32 + (255 - value) * 4, где value - значение байта

А вот зависимость высоты изображения от значения второго байта никак вывести не удается. Составил таблицу соответствий:

Но пока никакой логики не нашел. Для первых столбцов, вроде бы, подходит ([value:5-8] - 0x0C) * 32 + k, где value:5-8 - байт, образованные 4мя старшими битами, k - неизвестное слагаемое.
Но для столбца 8 она уже неверна. Да и k найти не удалось. Помогите добрым советом!

---

Для FF:CA подходит формула ([BYTE:5-8] - 0x0C) * 32 + ([BYTE:1-4] - 0x0A) * 2, но в остальных случаях даёт сбой.

---

Учитывая то, что формула кажется очень зыбкой и вероятно является совпадением, а в вычислениях могут участвовать и соседние биты, креплю архив с собственно файлами. Сам же продолжу копать...
[ATTACH]5185[/ATTACH]
332
14 июня 2011 года
Valiant
416 / / 27.09.2004
Очень полезно!!! Спасибо!!!
9.7K
15 июня 2011 года
Vitamant
228 / / 07.02.2011
Продолжая, диалог с самим собой, докладываю об успехах:

Высота определяется следующим образом:
Байт делится на две тетрады. Каждая из них представляется отдельным байтом и являет значение от 0 до 15. Этот коэффициент умножается на 32 в случае с первой тетрадой и на 2 - со второй. Получившиеся цифры складываются, в итоге получается высота.
Проблема в том, что данные уж больно хитро зашифрованы. И на данный момент легче нарисовать хэш-таблицу соответствий тетрады и коэффициента, однако это не очень красивое решение. так как данные в таблице все-таки имеют определенную структуру. В связи с этим вопрос: как инвертировать операцию, проведенную над байтами, так что бы значению 0x0 соответствовал коэффициент 0, а 0xF - 15? На словах все просто - поменять местами 1 и 2, 3 и 4 четверти, после чего также поменять местами 1 и 2 половины таблиц. Но как это реализовать на практике?

Код:
C 1100 - 0      A 1010 - 0
D 1101 - 1      B 1011 - 1
E 1110 - 2      8 1000 - 2
F 1111 - 3      9 1001 - 3
8 1000 - 4      E 1110 - 4
9 1001 - 5      F 1111 - 5
A 1010 - 6      C 1100 - 6
B 1011 - 7      D 1101 - 7
4 0100 - 8      2 0011 - 8
5 0101 - 9      3 0011 - 9
6 0110 - 10     0 0000 - 10
7 0111 - 11     1 0001 - 11
0 0000 - 12     6 0110 - 12
1 0001 - 13     7 0111 - 13
2 0010 - 14     4 0100 - 14
3 0011 - 15     5 0101 - 15


P.S. Как видно из таблице, представленные выше данные были ложными. В связи с этим, я сделал главный вывод и предостерегаю новичков: НИКОГДА не следует полностью верить данным, тем более графическим, которые выводит программа, над которой до этого бессовестно измывались! Она может отомстить (и обязательно это сделает)!

Valiant
Да, не за что!
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог