Update: This project was featured on Hackaday
This file is an NES ROM. It is also a ZIP file. This file fully functions as an NES ROM and a ZIP file at the same time.
What is in this ZIP file? The source code of this NES ROM.
What happens when you compile that source code? It creates an NES ROM that is also a ZIP file that contains the source code of that NES ROM.
This NES ROM can be burned onto an NES cartridge and it will work on an NES. Even if you rip the data from the cartridge, the NES ROM will still be the ZIP file.
The method I use to make an NES ROM that is also a ZIP file is NOT the same method used in issue 0x14 of PoC||GTFO. My method embeds the ZIP file inside the NES ROM and allows the NES ROM to be burned onto a cartridge while retaining the ZIP file data. The method used by PoC||GTFO stores the ZIP file data outside of the NES ROM file data, so issue 0x14 of PoC||GTFO can't be burned onto a cartridge while retaining the ZIP file data.
This NES ROM uses the iNES file format. The iNES file format is actually pretty simple.
There is an iNES header at the beginning of the NES ROM file that gives a little bit of information about the NES ROM so emulators can understand the NES ROM data. After the iNES header is the PRG data, which is the program logic data of the NES ROM. Then comes the CHR data, which is the background and sprite tilesets. Any empty space in the PRG is padded out and there may be a necessary few bytes at the end of the PRG data as well (this NES ROM has 6 necessary bytes at the end of the PRG data that I could not modify).
ZIP files have quite a few components to them, so we'll just focus on the parts that we have to care about.
For every file and directory that is contained in the ZIP file, a Central Directory File Header exists. Each Central Directory File Header can be found by looking for the header signature bytes 0x504B0102 in the ZIP file. The important bit of information is the Local Header Offset, as we'll be changing each offset when we embed the ZIP file into the NES ROM.
ZIP files actually figure out where the actual they start and end by going to the end of the file and moving towards the start until it finds the End of Central Directory Record signature bytes of 0x504B0506. The Central Directory Offset in the End of Central Directory Record is important for us to update when we embed the ZIP file into the NES ROM. We can also specify a ZIP File Comment Length and that amount of bytes after the end of the ZIP file data will be a ZIP File Comment.
If we find enough padding space in the PRG data, we can just replace the padding data with the ZIP file. For this NES ROM, I counted padding bytes from the end of the PRG data until I had enough space to embed the ZIP file, then I made note of how far into the NES ROM I started embedding the ZIP file. I then updated all of the offsets of the ZIP file data by adding how far into the NES ROM the ZIP file started. After that, I set the ZIP File Comment Length to be the size of the rest of the NES ROM data, which is the end of the PRG data and all of the CHR data.
This file remains an NES ROM because none of the necessary PRG data or any of the CHR data is disturbed. It is also a ZIP file because the offsets are all correct and any data after the ZIP file data is declared a ZIP File Comment.
Let's test the file at the beginning out to make sure it is both an NES ROM and a ZIP file. After downloading the file as an NES ROM, I make a copy of it.
Renaming the file allowed me to change the file extension from .nes to .zip
After changing the file extension to .zip, the file is now considered a ZIP file.
Unzipping this file produces a directory.
If we take a look at the contents of the directory, we'll find the source code of the file. We just took an NES ROM, changed it to a ZIP file and then unzipped it successfully.
I decided to have a little bit of fun with the ZIP file while I was updating the offsets of the ZIP file data. The Host OS that created the ZIP file is specified in the Central Directory File Headers, so I decided to have the ZIP file claim it was created on an Atari ST.
This part was actually the easiest part. The zipped up source code resulted in a ZIP file that was small enough to easily be embedded into the NES ROM, so I decided to make the NES/ZIP recursive. It didn't take that much work to automate the process of making the ZIP file of the source code or to automate the process of embedding the ZIP file into the NES ROM.
This project is merely a proof of concept to show that it is possible to embed a ZIP file into an NES ROM in a way that creates a file that is both the ZIP file and NES ROM at the same time and in a way that allows the data to be burned onto a cartridge and retain all of its properties.
While I decided to make this NES ROM compatible with NES-NROM-128 circuit boards because of its simplicity, this process should work for just about any NES ROM as long as there is sufficient amount of PRG data padding to embed the ZIP file into.
Different NES ROMs may need special handling to make this work, as different 6502 assemblers may pad their PRG data differently. I haven't tested this method out with more complicated NES games that have bank switching. I also did not test modifying the PRG data size to add enough padding to embed ZIP files larger than what is available.
With all of that said, if I hand you an NES cartridge, don't be surprised if there is a secret ZIP file embedded in the NES ROM data.
The source code for this project can be found at https://github.com/ViGrey/neszip-example (or by unzipping the NES ROM file) and is licensed under a BSD 2-Clause License.