Привет, Хабр! На днях я захотел достать ресурсы одной визуальной новеллы, созданной с помощью Ren'Py (Да, да, того самого "Бесконечного Лета"). Опытным путем было установлено, что все они хранятся в файле archive.rpa. Я нашел готовые скрипты для распаковки на Github, но решил достать их сам, Haskell в помощь... Во-первых, давайте разберемся с чем конкретно мы имеем дело, то есть как ресурсы хранятся в нашем "архиве". Откроем его в dhex или в аналогичной программе:  Что мы видим? Сначала данные о Ren'Py, потом еще что-то, а потом — начало PNG файла! Вот оно!
Долистаем до конца PNG:  Тип бинарного файла можно определить по его первым байтам (т. н. "магическому числу"). У PNG "магические" байты таковы: 89 50 4e 47 0d 0a 1a 0a. Конец же PNG файла тоже всегда одинаков: 49 45 4e 44 ae 42 60 82
Список "магических чисел" других форматов можно найти здесь Ага! PNG кончился, после него байты 4d 61 64 65 20 77 69 74 68 20 52 65 6e 27 50 79 2e (в ASCII: "Made with Ren'Py."), после чего — начало нового PNG. Вывод: данные ресурсов в .rpa архивах разделяются этой последовательностью байтов. Время подумать о программе. Итак, что же конкретно должна делать программа? Вот это: Начнем, пожалуй, с функции разбиения на файлы: Extractor.hs: Здесь magicSep — это те самые "магические" байты, разделяющие файлы ресурсов. Функция extractRes берет строку байтов, преобразует ее в список, разбивает его на списки байтов файлов, и каждый из этих списков преобразуется обратно в строку байтов. Остается один вопрос: зачем я использовал ленивую разновидность строк байтов? Ответ на него мы вскоре получим. ExtensionId.hs: Здесь мы видим две главные функции: readExtension и writeExtension. Несложно догадаться, что одна считывает первые байты файла и на их основе устанавливает расширение, а вторая — переименовывает файл в соответствии с ним. Всю грязную работу по определению расширения на себя берет функция magicLookup, которая просто проходит по списку известных "магических чисел" и сравнивает их с началом файла. magicMap представляет из себя список соответствий байтов и расширений, pngMagic, jpgMagic и oggMagic — "магические числа" форматов файлов. Их список может быть расширен читателем, но я точно знал, что кроме этих форматов в архиве ничего не встретится. Остался наш дорогой Main.hs: Что же здесь? Функция main просто проверяет количество аргументов, и если их два, то делегирует грязную работу (тоже не шибко чистой) функции extractToFolder, в противном случае посылает нас курить мануал читать usage. В extractToFolder творится примерно то же самое: проверка корректности ввода (защита от дурака), и если папка, в которую надо выводить, не существует или пользователь дал согласие на уничтожение ее содержимого, обращается за помощью к extractToFolder' В других языках способ обработки данных примерно такой: читаем все данные, после чего разбиваем их на части, после чего каждую записываем в файл и даем расширение. Haskell же предлагает несколько другой подход: обрабатывать данные по мере их поступления. Это возможно благодаря ленивым вычислениям (вот почему я использовал ленивые строки байтов). Такой подход позволяет пользователю лучше видеть процесс обработки, что уже неплохо. extractToFolder' — своеобразный конвейер, на котором и происходит обработка архива: читаем файл, разбиваем его на части, пока части не кончатся записываем их в файлы с уникальными именами. Ну вот и все, набираем в терминале заветные команды: И наслаждаемся ловко стыренным искусно добытым контентом!
Вопросы, жалобы, предложения — прошу в комментарии!