The N0RAM project project has received a series of upgrades since its initial functionality as a serial bootloader. As a last bit of functionality I’ve decided to make N0RAM suitable for a BIOS replacement, able to load a ROM via serial port as it already does, and attempting to load a ROM from the other available sources (Cartridge/Card/External) if available.
Feature already available on the N0RAM project starting at V1.3
Adapt the existing N0RAM code to make it suitable as BIOS replacement. It should be able to perform these steps in this order.
- Attempt to download a ROM via serial port
- If unsuccesful, either:
- By default, autodetect ROM media and launch it
- On user request, launch one of the ROM media unconditionally
- Finally, if all fails, prompt user to Reset the console
Original SEGA BIOS functionality
The original SEGA BIOS functionality, for export (non-JP) consoles is checking whether each of the media slots, EXT/Card/Cartridge, has a valid ROM inside of it, then chainload into it. The detailed inner workings of all known SEGA BIOS found on consoles is described at smspower.org but can be resumed in:
- Checking the cartridge checksum
- Set up the SEGA mapper
- Write the last value of port
0x3Eat the RAM base address
Only the last two behaviours are required to be emulated to avoid compatibility issues.
Replacing the SEGA BIOS
N0RAM itself can run from any media slot, be it BIOS or any of the console slots so the only real issue is the ROM detection and chainload routines, which will have to be run from RAM, since the ROM will not be available during detection.
In essence, the chainloader code has these restrictions.
- It must run from RAM, limitation by console’s design
- It must run from any RAM address
- It must prevent chainloading itself when not running as BIOS
Just like with N0RAM’s requirement of zero RAM usage, this code while not restricted by RAM usage, implies thse further restrictions
- No absolute jumps with
- All jumps limited to a maximum 127 bytes for use with
- All RAM addressing must be Stack-relative, via
ADD HL, SPor similar.
Code from RAM
For the chainloader code location I’ve decided the safest place to locate it for future extensions would be the stack itself. In this way, the code can be loaded, called to, and then disposed off just by modifying the SP reg.
Currently, the chainloader loading routine would look like this:
Original stack: SP --> [N0RAMboot return addr] //Return addres to go back to N0RAM After copying loader code to stack SP --> [Chainloader code ] [N0RAMboot return addr]
At this point we save the current value of
SP to jump to it later, then we
push into the stack the arguments for the chainloader code (see: Loader
detection further down) and the return address for if the chainloader fails
to detect any ROM, give control back to the BIOS.
SP --> [Chainload return addr] [Chainload arguments ] [Chainloader code ] //Stored on a reg elsewhere, DE for example [N0RAMboot return addr]
Finally, to jump into the chainloader from an stored register we simply execute:
PUSH DE ;DE having the chainloader code base address RET ;Use RET to jump to the PUSHed address
In the event the chailoader fails, it will return and we can continue our business simply by fixing the SP register.
The original SEGA way of detecting a ROM is by checking the SEGA ROM header but to allow loading of any program I opted instead on testing that:
- Two reads of the first 256 Bytes are equal (same CRC)
- At least 16 byte-to-byte differences are encountered
This has prevented booting any empty slot and has not prevented any real ROM from being booted so far.
N0RAM can boot from regular media, be it card, cartridge or external port. For this reason, detecting wether a ROM media is “ourselves” is requires as to not automatically load ourselves in a loop when there might be valid media on other slots.
The solution implemented is passing a “signature” blob of bytes as an argument to the chainloader and, if it detects the given the ROM under test has the same signature at the expected place, it will skip the media during the automatic chainloader.
On the particular case implemented on N0RAM, the signature are 14 random bytes +2 Bytes CRC, as to allow detection with both direct comparison or using a CRC. Current revision directly compares the signature.