Этот файл — ROM консоли NES. И одновременно ZIP-файл. Он одновременно полностью функционален и как NES ROM, и как ZIP-файл.
Что находится в этом ZIP-файле? Исходный код ROM.
Что случится, если скомпилировать этот исходный код? Он создаст NES ROM, который также является ZIP-файлом, содержащим исходный код этого NES ROM.
![](https://habrastorage.org/getpro/habr/post_images/456/6be/0f7/4566be0f7939367c0d79ca42360d000f.png)
Запущенный ROM
Этот NES ROM можно «прожечь» на картридж NES и он будет работать на NES. Даже если конвертировать все данные с картриджа, образ NES всё равно будет ZIP-файлом.
Выпуск 0x14 журнала PoC||GTFO был одновременно PDF, ZIP-файлом и NES ROM. Именно этот выпуск вдохновил меня на создание NES-игры с нуля для Tymkrs.
Способ, использованный мной для создания NES ROM, также являющегося ZIP-файлом — это НЕ тот же способ, который использовался в выпуске issue 0x14 PoC||GTFO. Мой способ встраивает ZIP-файл в NES ROM и позволяет записать образ NES на картридж, сохранив данные ZIP-файла. В методе, использованном PoC||GTFO данные ZIP-файла хранятся за пределами файла ROM NES, поэтому выпуск 0x14 PoC||GTFO нельзя записать на картридж с сохранением данных ZIP-файла.
В этом образе NES используется формат файлов iNES. Формат файлов iNES на самом деле довольно прост.
В начале ROM есть заголовок iNES, который сообщает немного информации о NES ROM, чтобы эмуляторы могли понимать данные образа NES. После заголовка iNES следуют данные PRG, являющиеся данными программной логики NES ROM. Затем идут данные CHR, то есть наборы тайлов фона и спрайтов. Всё пустое пространство в PRG заполнено отступами, кроме того, в конце данных PRG может быть несколько байт (в этом NES ROM есть 6 необходимых байт в конце данных PRG, которые я не могу изменять).
![](https://habrastorage.org/getpro/habr/post_images/836/5ca/6b1/8365ca6b10d2ef06c8a1e1ecbc1f660b.png)
Формат файлов iNES
В файлах ZIP достаточно много компонентов, поэтому мы сосредоточимся тех частях, которые для нас важны.
Для каждого файла и каталога, содержащегося в ZIP-файле, существует заголовок файла центрального каталога (Central Directory File Header). Любой заголовок файла центрального каталога можно найти, выполнив поиск байтов сигнатуры заголовка 0x504B0102 в ZIP-файле. Важная часть информации — это локальное смещение заголовка (Local Header Offset), поскольку при встраивании ZIP-файла в NES ROM мы будем менять каждое смещение.
![](https://habrastorage.org/getpro/habr/post_images/981/8b7/17c/9818b717c36ae5e646c94ae92d3a1af5.png)
Формат заголовка файла центрального каталога ZIP
ZIP-файлы определяют своё начало и конец, переходя в конец файла и двигаясь к началу, пока не дойдут до байтов 0x504B0506 сигнатуры конца записи центрального каталога (End of Central Directory Record). При встраивании ZIP-файла в NES ROM нам важно обновить смещение центрального каталога (Central Directory Offset) в конце записи центрального каталога. Также мы можем указать длину комментария ZIP-файла (ZIP File Comment Length) и это количество байтов после конца данных ZIP-файла будет комментарием ZIP-файла.
![](https://habrastorage.org/getpro/habr/post_images/704/24d/b0a/70424db0a21d547bf7f4ad783f32a8f0.png)
Формат конца записи центрального каталога ZIP
Если мы найдём достаточно отступов в данных PRG, то сможем просто заменить эти пустые данные ZIP-файлом. В своём NES ROM я отсчитывал байты отступов от конца данных PRG, пока не получил достаточно места для встраивания ZIP-файла и записал, насколько далеко в NES ROM я начал встраивать ZIP-файл. Затем я обновил все смещения данных ZIP-файла, прибавив расстояние, на котором начинается в образе NES ZIP-файл. После этого я задал размер длины комментария ZIP-файла равным размеру оставшейся части данных NES ROM, то есть концу данных PRG и всем данным CHR.
![](https://habrastorage.org/getpro/habr/post_images/fca/b92/87f/fcab9287f773ac223d6e960286b511cc.png)
Формат файла NESZIP
Этот файл остаётся образом NES, потому что никакие из необходимых данных PRG и данные CHR не повреждены. Также он является ZIP-файлом, потому что все смещения верны и все данные после данных ZIP-файла объявлены комментарием ZIP-файла.
Давайте сначала протестируем файл, чтобы убедиться, что он одновременно является и NES ROM, и ZIP-файлом. Скачав файл как NES ROM, я делаю его копию.
![](https://habrastorage.org/getpro/habr/post_images/426/512/005/4265120055a39595e5cbd53e1a70f789.png)
Копия NES ROM
Переименование файла позволяет мне изменить расширение с .nes на .zip.
![](https://habrastorage.org/getpro/habr/post_images/59b/5e5/5a5/59b5e55a586daee68a92cea71e0d46e9.png)
Меняем расширение копии NES ROM на .zip
После замены расширения на .zip, файл считается ZIP-файлом.
![](https://habrastorage.org/getpro/habr/post_images/a65/969/61c/a6596961c1ac83e11b79144fb822f52a.png)
При распаковке этого файла создаётся каталог.
![](https://habrastorage.org/getpro/habr/post_images/9bd/f33/d28/9bdf33d28a43e0c944b64f957787511b.png)
Если мы посмотрим на содержимое каталога, то увидим исходный код файла. Мы просто взяли NES ROM, переименовали его в ZIP-файл и успешно его распаковали.
![](https://habrastorage.org/getpro/habr/post_images/dfa/096/08a/dfa09608a062bd3c62af7f57463e8593.png)
Обновляя смещения данных ZIP-файла, я решил немного развлечься с этим ZIP-файлом. В заголовках файлов центрального каталога указывается ОС (Host OS), в которой создан ZIP-файл, поэтому я решил сделать так, чтобы ZIP-файл утверждал, что был создан на Atari ST.
![](https://habrastorage.org/getpro/habr/post_images/8a8/410/45d/8a841045d0f5fc82fd606a8d5b7f96be.png)
На самом деле, эта часть — самая простая. Запакованный исходный код стал ZIP-файлом, достаточно маленьким, чтобы его можно было легко встроить в NES ROM, поэтому я решил сделать NES/ZIP рекурсивными. Для автоматизации процесса создания ZIP-файла исходного кода или для автоматизации процесса встраивания ZIP-файла в NES ROM потребовалось не так много труда.
Этот проект является простым proof of concept, демонстрирующим возможность встраивания ZIP-файла в NES ROM способом, создающим файл, который одновременно становится и ZIP-файлом, и образом NES, и позволяющим записывать данные на картридж с сохранением всех их свойств.
Так как я решил сделать этот NES ROM совместимым с печатными платами NES-NROM-128 (из-за их простоты), процесс будет работать практически для любого NES ROM, если в нём будет достаточное количество отступов в данных PRG для встраивания ZIP-файла.
Чтобы реализовать этот процесс на других NES ROM, может потребоваться дополнительная работа, потому что разные ассемблеры 6502 могут заполнять отступами данные PRG по-разному. Я не тестировал этот метод с более сложными играми NES, имеющими переключение банков. Также я не тестировал возможность добавления отступов в размер данных PRG для встраивания ZIP-файлов большего размера.
С учётом всего сказанного, не удивляйтесь, если я дам вам картридж NES с тайным ZIP-файлом, скрытым в данных NES ROM.
Исходный код этого проекта выложен на GitHub (или его можно получить, распаковав файл NES ROM) и имеет лицензию BSD 2-Clause License.
Что находится в этом ZIP-файле? Исходный код ROM.
Что случится, если скомпилировать этот исходный код? Он создаст NES ROM, который также является ZIP-файлом, содержащим исходный код этого NES ROM.
![](https://habrastorage.org/getpro/habr/post_images/456/6be/0f7/4566be0f7939367c0d79ca42360d000f.png)
Запущенный ROM
Этот NES ROM можно «прожечь» на картридж NES и он будет работать на NES. Даже если конвертировать все данные с картриджа, образ NES всё равно будет ZIP-файлом.
Источник вдохновения
Выпуск 0x14 журнала PoC||GTFO был одновременно PDF, ZIP-файлом и NES ROM. Именно этот выпуск вдохновил меня на создание NES-игры с нуля для Tymkrs.
Способ, использованный мной для создания NES ROM, также являющегося ZIP-файлом — это НЕ тот же способ, который использовался в выпуске issue 0x14 PoC||GTFO. Мой способ встраивает ZIP-файл в NES ROM и позволяет записать образ NES на картридж, сохранив данные ZIP-файла. В методе, использованном PoC||GTFO данные ZIP-файла хранятся за пределами файла ROM NES, поэтому выпуск 0x14 PoC||GTFO нельзя записать на картридж с сохранением данных ZIP-файла.
Формат файлов NES ROM
В этом образе NES используется формат файлов iNES. Формат файлов iNES на самом деле довольно прост.
В начале ROM есть заголовок iNES, который сообщает немного информации о NES ROM, чтобы эмуляторы могли понимать данные образа NES. После заголовка iNES следуют данные PRG, являющиеся данными программной логики NES ROM. Затем идут данные CHR, то есть наборы тайлов фона и спрайтов. Всё пустое пространство в PRG заполнено отступами, кроме того, в конце данных PRG может быть несколько байт (в этом NES ROM есть 6 необходимых байт в конце данных PRG, которые я не могу изменять).
![](https://habrastorage.org/getpro/habr/post_images/836/5ca/6b1/8365ca6b10d2ef06c8a1e1ecbc1f660b.png)
Формат файлов iNES
Формат файлов ZIP
В файлах ZIP достаточно много компонентов, поэтому мы сосредоточимся тех частях, которые для нас важны.
Для каждого файла и каталога, содержащегося в ZIP-файле, существует заголовок файла центрального каталога (Central Directory File Header). Любой заголовок файла центрального каталога можно найти, выполнив поиск байтов сигнатуры заголовка 0x504B0102 в ZIP-файле. Важная часть информации — это локальное смещение заголовка (Local Header Offset), поскольку при встраивании ZIP-файла в NES ROM мы будем менять каждое смещение.
![](https://habrastorage.org/getpro/habr/post_images/981/8b7/17c/9818b717c36ae5e646c94ae92d3a1af5.png)
Формат заголовка файла центрального каталога ZIP
ZIP-файлы определяют своё начало и конец, переходя в конец файла и двигаясь к началу, пока не дойдут до байтов 0x504B0506 сигнатуры конца записи центрального каталога (End of Central Directory Record). При встраивании ZIP-файла в NES ROM нам важно обновить смещение центрального каталога (Central Directory Offset) в конце записи центрального каталога. Также мы можем указать длину комментария ZIP-файла (ZIP File Comment Length) и это количество байтов после конца данных ZIP-файла будет комментарием ZIP-файла.
![](https://habrastorage.org/getpro/habr/post_images/704/24d/b0a/70424db0a21d547bf7f4ad783f32a8f0.png)
Формат конца записи центрального каталога ZIP
Прячем ZIP-файл в NES ROM
Если мы найдём достаточно отступов в данных PRG, то сможем просто заменить эти пустые данные ZIP-файлом. В своём NES ROM я отсчитывал байты отступов от конца данных PRG, пока не получил достаточно места для встраивания ZIP-файла и записал, насколько далеко в NES ROM я начал встраивать ZIP-файл. Затем я обновил все смещения данных ZIP-файла, прибавив расстояние, на котором начинается в образе NES ZIP-файл. После этого я задал размер длины комментария ZIP-файла равным размеру оставшейся части данных NES ROM, то есть концу данных PRG и всем данным CHR.
![](https://habrastorage.org/getpro/habr/post_images/fca/b92/87f/fcab9287f773ac223d6e960286b511cc.png)
Формат файла NESZIP
Этот файл остаётся образом NES, потому что никакие из необходимых данных PRG и данные CHR не повреждены. Также он является ZIP-файлом, потому что все смещения верны и все данные после данных ZIP-файла объявлены комментарием ZIP-файла.
Давайте сначала протестируем файл, чтобы убедиться, что он одновременно является и NES ROM, и ZIP-файлом. Скачав файл как NES ROM, я делаю его копию.
![](https://habrastorage.org/getpro/habr/post_images/426/512/005/4265120055a39595e5cbd53e1a70f789.png)
Копия NES ROM
Переименование файла позволяет мне изменить расширение с .nes на .zip.
![](https://habrastorage.org/getpro/habr/post_images/59b/5e5/5a5/59b5e55a586daee68a92cea71e0d46e9.png)
Меняем расширение копии NES ROM на .zip
После замены расширения на .zip, файл считается ZIP-файлом.
![](https://habrastorage.org/getpro/habr/post_images/a65/969/61c/a6596961c1ac83e11b79144fb822f52a.png)
При распаковке этого файла создаётся каталог.
![](https://habrastorage.org/getpro/habr/post_images/9bd/f33/d28/9bdf33d28a43e0c944b64f957787511b.png)
Если мы посмотрим на содержимое каталога, то увидим исходный код файла. Мы просто взяли NES ROM, переименовали его в ZIP-файл и успешно его распаковали.
![](https://habrastorage.org/getpro/habr/post_images/dfa/096/08a/dfa09608a062bd3c62af7f57463e8593.png)
Обновляя смещения данных ZIP-файла, я решил немного развлечься с этим ZIP-файлом. В заголовках файлов центрального каталога указывается ОС (Host OS), в которой создан ZIP-файл, поэтому я решил сделать так, чтобы ZIP-файл утверждал, что был создан на Atari ST.
![](https://habrastorage.org/getpro/habr/post_images/8a8/410/45d/8a841045d0f5fc82fd606a8d5b7f96be.png)
Создаём рекурсию
На самом деле, эта часть — самая простая. Запакованный исходный код стал ZIP-файлом, достаточно маленьким, чтобы его можно было легко встроить в NES ROM, поэтому я решил сделать NES/ZIP рекурсивными. Для автоматизации процесса создания ZIP-файла исходного кода или для автоматизации процесса встраивания ZIP-файла в NES ROM потребовалось не так много труда.
Подводим итог
Этот проект является простым proof of concept, демонстрирующим возможность встраивания ZIP-файла в NES ROM способом, создающим файл, который одновременно становится и ZIP-файлом, и образом NES, и позволяющим записывать данные на картридж с сохранением всех их свойств.
Так как я решил сделать этот NES ROM совместимым с печатными платами NES-NROM-128 (из-за их простоты), процесс будет работать практически для любого NES ROM, если в нём будет достаточное количество отступов в данных PRG для встраивания ZIP-файла.
Чтобы реализовать этот процесс на других NES ROM, может потребоваться дополнительная работа, потому что разные ассемблеры 6502 могут заполнять отступами данные PRG по-разному. Я не тестировал этот метод с более сложными играми NES, имеющими переключение банков. Также я не тестировал возможность добавления отступов в размер данных PRG для встраивания ZIP-файлов большего размера.
С учётом всего сказанного, не удивляйтесь, если я дам вам картридж NES с тайным ZIP-файлом, скрытым в данных NES ROM.
Исходный код
Исходный код этого проекта выложен на GitHub (или его можно получить, распаковав файл NES ROM) и имеет лицензию BSD 2-Clause License.
Комментарии (4)
Mingun
16.02.2018 20:53Гм. На самом деле, получается, zip-файл можно встроить практически куда угодно, было бы в этом «куда-угодно» достаточно места под заголовки zip-файла, верно?
divanikus
Чувак rarjpeg изобрел?
dmitryredkin
Переосмыслил.