******************************************************* *Famicom Disk System ROM BIOS & I/O port documentation* ******************************************************* Brad Taylor (big_time_software@hotmail.com) second release October 25, 2002 first release September 26, 2002 started September 21, 2002 +------+ |Thanks| +------+ to Nori and Goroh for their existing related docs A special thanks to "D" for his preliminary ROM BIOS documentation he posted on NESdev, a long time ago. +----------+ |Disclaimer| +----------+ I formally disclaim any responsibilities that may come to the user(s) of the data contained in this document (i.e., use at your own risk). +----------------+ |Context accuracy| +----------------+ The information herein this document is considered to be accurate. However, since I have not done a full reverse engineering of the RAM adaptor hardware, nor have I had access to any kind of related schematics or otherwise electronic blueprints, some of the related info here is based on educated guesses (and in the worst case, speculation). If you, the reader, see a data inaccuracy in this document, I would strongly encourage you to contact me in regards to it. I will repair the mistake, and award you credit in the next release of this document. +----------------------------+ |What this document describes| +----------------------------+ - how this document came to be - general information on the RAM adaptor hardware - the RAM adaptor's disk related hardware facilities - ROM BIOS general information - the steps involved in booting an FDS disk - proper procedures for low-level disk I/O port programming - how disk system games interact with the ROM BIOS - ROM BIOS disk I/O interface routines and structures - ROM BIOS disk I/O routine emulation considerations note: terms used in this document (specifically relating to data (block) storage on FDS disks) assume you have already become familiar with prior information available on FDS. Please consult one of the below mentioned documents for information not listed in this one. +------------------------------+ |Existing FDS related documents| +------------------------------+ * Famicom Disk System info * author: "D" date: Monday, February 16, 1998 02:45:10 location: NESdev archives description: This document could be considered to be the granddaddy of FDS technical documents, as there were none available prior to it's development. Prehaps may even have been the catalyst to all the FDS research & develoment that followed. Describes some of the FDS's ROM BIOS subroutines, data areas, and even I/O ports. This document is incomplete, and in some places, inaccurate. However, the document is very comprehensive, and still contains some information no other currently available FDS related document does. * Disk System Infomation * author: Goroh translator: Sgt. Bowhack date: 1998/05/17 descripton: The first complete FDS technical document to be released. Describes how data structures are layed out on an FDS disk. Also breifly describes the FDS's I/O ports, disk errors, control signal pinouts, and emulator implementation. At it's time, was the best source for FDS info available. However, the document is very brief, and lacks proper details almost everywhere. * FDS info * author: Nori translator: Ki date: unknown description: An original document which is an improvement on Goroh's existing one. Additionally, mentions the low-level (physical) layout of data stored on FDS disks (mentions the "GAP" periods). * Getting FDS disk games to boot that normally don't * author: (myself) date: Friday, March 22, 2002 01:04:57 description: discusses how to "tweak" the FDS disk drive to get disk games to work more reliably. * Famicom Disk System Disk Drive/RAM Adaptor Technical Briefing * author: (myself) date: Monday, April 29, 2002 17:17:26 description: Details the low-level serial protocol used (including the CRC algorithm used) for data transfers between the RAM adaptor & disk drive, and the physical data storage layout of a disk. Also details the disk drive's control signals (pin nomenclature), and steps toward emulating the drive itself. * FDS loader software documentation * author: (myself) date: Monday, July 01, 2002 00:02:30 description: Documents a software project which interfaces the IBM PC/compatable with the FDS RAM adaptor (or even the FDS disk drive). The software essentially emulates the operations of a real FDS disk drive, so that FDS games can be stored on/played directly off a PC (without the requirement of an FDS disk drive). Very curious FDS enthusiasts will want to check this project out not just for what it does, but also because the full source code to the software's operation has also been included (x86 assembly language proficiency is required). All aforementioned literature can be found at http://nesdev.parodius.com. ***************************************** *Document inspiration/Tribute to the FDS* ***************************************** For many NES/famicom enthusiasts that have had the pure priveledge of seeing the Famicom Disk System first hand, the reaction always seems to be the same: WOW! Even when considering the limitations of the hardware (and it's short-lived product cycle), it's not very hard to understand the logic behind the reason the Famicom Disk System (FDS) system continues to fascinate everyone who has ever seen it in action: It turns the NES/FC into a real computer system. Using floppy disks to boot the NES/FC up into a favorite game is somthing a person has to see for themselves to really appreciate. Additionally, the very first Nintendo games to have any kind of progress-saving features appeared in the FDS format (people who had never seen the FDS versions of games like Kid Icarus, Metroid, and Castlevania are always very impressed by the fact that all these games were released originally on the FDS with save features, opposed to the password system their cart counterparts had (they didn't even put a password system in the cart version of Castlevania!)). Finally, the conventional sound of NES/FC games can almost be considered "crude" when compared to games which take advantage of the extra sound channel present in the FDS. People who have experienced the sound first hand will agree that it really adds a new dimension to the gaming experience. Bottom line: the FDS is one cool and unique piece of hardware. After completing the FDS loader project (which I was very pleased with), I thought I had crossed the final frontier for preserving the legacy of the FDS (since part of my project's goal was to be able to play FDS games on an authentic NES/FC without having to further degrade the floppy disks the games exist on). This was until I started to think about designing a direct interface between the FDS RAM adaptor and a hard drive (or even a 3.5" floppy drive), and scrapping the PC's intervention alltogether. The biggest problems with doing this was the fact that the RAM adaptor not only transfers data at a very sluggish 12KB/sec, but the RAM adaptor transfer protocol is totally incompatible with any of the common transfer protocols found on floppies or hard drives (for example, standard 3.5" floppies store data in 4096-bit blocks (sectors), but FDS disk data storage blocks are dynamic in length). To get around the incompatabilities, a program similar to the one I wrote for the PC would have to be written for a microcontroller, which would then serve as the interface between the RAM adaptor, and the hard drive (or the like). Since I'm a man of efficiency, I thought that using a microcontroller that has more memory and more processing power than the thing I'm interfacing it to was a ridiculous idea. So I started to wonder how to get around using the RAM adaptor for disk communication alltogether. It's a known fact that most software (games) written for the FDS don't do the disk access themselves; they rely on the ROM BIOS to do the low level disk access. All the game does is provide the file name(s) (or number(s) of files) for the ROM BIOS to access. What is not common knowledge is the details as to how games do this. Basically, I thought that if I could crack this interface, then I could re-write the ROM BIOS code so that when the game requests disk access, my routines would handle the call in the same mannar as the conventional BIOS, but instead of using the FDS's disk I/O ports for transferring data between disk, I'd be using the I/O ports of an IDE hard drive mapped into the NES/FC's memory map. So that was my goal this time around: studying the FDS's ROM BIOS code, and figuring out how the hell games access the disk subroutines. Needless to say, this goal had been realized, and thus why I've written another (and probably final) FDS related documentation. Enjoy. ******************************* *Brief RAM adaptor information* ******************************* The RAM adaptor is a piece of hardware which interfaces the NES/famicom system hardware with the FDS disk drive. What you'll find inside the RAM adaptor is a custom 32KB DRAM chip, an 8KB RAM chip for the pattern table memory, some circuitry for mixing the FDS's audio channel in with the system audio, and the heart of the RAM adaptor: the 2C33 chip. The DRAM chip (which is mapped in at $6000..$DFFF) has alot of connections exclusively to the 2C33 chip. Obviously the 2C33 is controlling the DRAM's refresh cycles since ALL the system address lines go into it (only a few system address lines actually are connected directly to the DRAM). DRAM timings are a mystery, however it is obviously capable of being randomly accessed every bus clock cycle. The BIOS ROM (mapped in at $E000..$FFFF) is integrated into either the DRAM chip (unlikely), or the 2C33 chip (very likely). It's definite location however is unknown (not that it really matters, anyway). The 8KB RAM chip used for pattern table memory is completely mapped into PPU address space $0000..$1FFF. The 2C33 can control name table mirroring, but other than that, there is no other hardware in the RAM adaptor pertaining to graphics control. The heart of the RAM adaptor, the 2C33, contains all the circuitry related to disk I/O, and the extra sound channel. *********************************** *Disk related hardware in the 2C33* *********************************** Disk hardware in the 2C33 is pretty much there only for processing & dispatching the serial data that appears on the data in & out wires. All other control signals directly reflect the binary values that FDS ports are programmed with (and vice-versa). There is some electronics used inside the 2C33 for converting RAW serial data to the protocol used for storing binary data on a magnetic disk (and vice-versa). Keep this in mind; in the port descriptions later, it suggests that the disk data inputs/outputs are connected directly to internal shift registers. However, this is only to simplify things. In reality, the disk data is treated before entering/leaving the RAM adaptor. Furthermore, the raw serial data read off a disk also contains the clock rate which the 2C33 uses to clock some of the shift registers by. The disk related hardware inside the 2C33 include: - 8-bit serial out/parallel in shift register (SR) (for serializing data to store on disk) - 8-bit serial in/parallel out SR (for assembling serial data from disk) - 16-bit cyclic redundancy check (CRC) SR (poly=10001000000100001b (the X25 standard)) - 4-bit SR being used as a johnson counter (this counter keeps track of the bit count) Note: This document will not go into further details on the internal architecture of the 2C33 (since I don't have any real blueprints of the 2C33). There may be other hardware (like additional shift registers, etc.) present in the 2C33 that I'm unaware of. This architectural information is only provided to give the programmer an idea of what's going on inside the 2C33. Disk Ports ---------- - Ports $402x are Write-Only - Ports $403x are Read-Only Only the disk-related ports are mentioned here. Please consult another document for information on other FDS ports (like sound, timer, etc.). +------+ |$4024:| +------+ Write data register. The data that this register is programmed with will be the next 8-bit quantity to load into the shift register (next time the byte transfer flag raises), and to be shifted out and appear on pin 5 of the RAM adaptor cable (2C33 pin 52). +-----+ |$4025| +-----+ FDS control. bit description --- ----------- 0: Electrically connected to pin C on RAM adaptor cable (2C33 pin 48). When active (0), causes disk drive motor to stop. During this time, $4025.1 has no effect. 1: Electrically connected to pin 3 on RAM adaptor cable (2C33 pin 49). When active (0), causes disk drive motor to turn on. This bit must stay active throughout a disk transfer, otherwise $4032.1 will always return 1. When deactivated, disk drive motor stays on until disk head reaches most inner track of disk. 2: Electrically connected to pin 1 on RAM adaptor cable (2C33 pin 50). Controls the disk data transfer direction. Set to 1 to read disk data, 0 to write. 3: Mirroring control. 0 = horizontal; 1 = vertical. 4: CRC control. ROM BIOS subroutines set this bit while processing the CRC data at the end of a block. While it is unclear why this bit is set during block reading, when this bit is set during a block write, this instructs the 2C33 to pipe the current contents of the CRC register to the disk (data in $4024 is effectively ignored during this time). 5: Always set to 1 (use unknown) 6: This bit is typically set while the disk head is in a GAP period on the disk. When this is done, it issues a reset to the 2C33's internal CRC accumulator. During reads, setting this bit instructs the 2C33 to wait for the first set bit (block start mark) to be read off the disk, before accumulating any serial data in the FDS's internal shift registers, and setting the byte transfer ready flag for the first time (and then every 8-bit subsequent transfer afterwards). During writes, setting this bit instructs the 2C33 to immediately load the contents of $4024 into a shift register, set the byte transfer flag, start writing the data from the shift register onto the disk, and repeat this process on subsequent 8-bit transfers. While this bit is 0, data in $4024 is ignored, and a stream of 0's is written to the disk instead. 7: When set, generates an IRQ when the byte transfer flag raises. +-----------+ |$4026/$4033| +-----------+ External connector output/input, respectfully. The outputs of $4026 (open-collector with 4.7K ohm pull-ups (except on bit 7)), are shared with the inputs on $4033. bit 2C33 ext.con --- ---- ------- 0 44 3 1 43 4 2 42 5 3 41 6 4 40 7 5 39 8 6 38 9 7 37 - Bit 7 here is used to report the status of the drive's power condition (1 = power good). It is electrically connected to pin 6 on the RAM adaptor cable. $4026 bit 7 must be set to 1 before the battery status can be checked via $4033 (otherwise it will always return 0). +-----+ |$4030| +-----+ 2C33 status. bit description --- ----------- 0: related to the IRQ timer registers (not described here). 1: Byte transfer flag. Set every time 8 bits have been transfered between the RAM adaptor & disk drive (service $4024/$4031). Reset when $4024, $4031, or $4030 has been serviced. 4: clear if the CRC register contains 0 (indicating that the transfer passed the CRC). 6: Unclear operation. Prehaps relates to $4032.1. 7: Unclear operation. Prehaps relates to $4023.0. +-----+ |$4031| +-----+ Read data register. This register is loaded with the contents of an internal shift register every time the byte transfer flag raises. The shift register recieves it's serial data via pin 9 of the RAM adaptor cable (2C33 pin 51). +-----+ |$4032| +-----+ Disk drive status. bit description --- ----------- 0: Electrically connected to pin A on RAM adaptor cable (2C33 pin 45). When active (0), indicates that a disk is inserted in the drive. 1: Electrically connected to pin B on RAM adaptor cable (2C33 pin 46). On the negative transition of this signal (-_), indicates that the drive head is currently at the most outer track (beginning of the disk). This bit will stay 0 until the disk drive head advances to the most inner track (end of disk), or if $4025.1 is 1 at anytime. 2: Electrically connected to pin 7 on RAM adaptor cable (2C33 pin 47). When active (0), indicates that a disk is inserted & is writable (as opposed to being read-only). 6: considered to return 1 ********** *ROM BIOS* ********** When the FDS is turned on, what you see & hear (the flashing Nintendo logo, Mario & Luigi running around, etc.) is being run off the ROM BIOS code. The ROM BIOS code is 8K bytes in size, and resides in the CPU memory map at $E000..$FFFF. There are dozens of subroutines inside the BIOS, but this document will focus on the disk interface subroutines (described later). BIOS data area -------------- The BIOS uses several places in memory, but only some of them are expected to be maintained by game code. They are as follows ([] = 8 bits; () = 16 bits). ($DFFE): disk game IRQ vector (if [$0101] = 11xxxxxxB) ($DFFC): disk game reset vector (if ($0102) = $5335, or $AC35) ($DFFA): disk game NMI vector #3 (if [$0100] = 11xxxxxxB) ($DFF8): disk game NMI vector #2 (if [$0100] = 10xxxxxxB) ($DFF6): disk game NMI vector #1 (if [$0100] = 01xxxxxxB) ($0102): PC action on reset [$0101]: PC action on IRQ. set to $80 on reset [$0100]: PC action on NMI. set to $C0 on reset [$FF]: value last written to [$2000] $80 on reset. [$FE]: value last written to [$2001] $06 on reset [$FD]: value last written to [$2005]#1 0'd on reset. [$FC]: value last written to [$2005]#2 0'd on reset. [$FB]: value last written to [$4016] 0'd on reset. [$FA]: value last written to [$4025] $2E on reset. [$F9]: value last written to [$4026] $FF on reset. The memory in $F9..$FF is always kept in sync with with the aforementioned ports. This is done because these ports are write-only. Consequentially, the current value these ports are programmed with can always be read here. There may be more structured data areas in the zero page (for example, the BIOS joypad routines use $F5..$F8 for storing controller reads), but only the listed ones are used by the disk call subroutines. Booting a disk game ------------------- Once a disk's boot files have been loaded successfully into memory (more on this later), ($0102) is assigned $AC35, and the BIOS data area (and their respective ports) are set to the aforementioned reset values. Finally, interrupts are enabled, and program control is transfered to the ($DFFC) vector. ************************************** *ROM BIOS disk procedures information* ************************************** Pretty much all the disk-related BIOS code resides in the range [$E1C7..$E7BA], so keep that in mind when looking at a ROM BIOS disassembly. - The ROM BIOS has disk routines, and disk subroutines. The routines are the procedures that provide the interface to the software, and the subroutines provide the routines the interface to the disk hardware. Later in this document, all known disk routines, and important disk subroutines will be documented. +-------------------------+ |disk subroutine data area| +-------------------------+ All ROM BIOS disk subroutines use a small amount of zero page memory. Disk routines usually pass parameters to the subroutines via this memory area. Disk routines do NOT save any data residing in this area prior to calling subroutines, so procedures that call disk routines must be sure NOT to keep important data in this area during the call. The following list describes the zero page memory used by the subroutines, and their common use ([] = 8 bits; () = 16 bits). ($00) first hardcoded parameter ($02) second hardcoded parameter [$04] previous stack frame [$05] error retry count [$06] file counter [$07] current block type [$08] boot ID code [$09] not 0 to make dummy reads ($0A) destination address ($0C) byte xfer count [$0E] file found counter Aside from this memory, disk subroutines also expect that the ROM BIOS data area ($F9..$FF) is maintained properly. +------------------+ |common disk errors| +------------------+ When a disk I/O operation fails (for one reason or another), an error # is generated, reflecting the nature of the failure. The common error #'s used are as follows (special error numbers will be mentioned later). 00: disk I/O successful (no error) 01: ($4032.0) disk not set 02: ($4033.7) power supply failure (i.e., battery) 03: ($4032.2) disk is write protected 21: '*NINTENDO-HVC*' string in block 1 doesn't match 22: block type 1 expected 23: block type 2 expected 24: block type 3 expected 25: block type 4 expected 27: ($4030.4) block failed CRC 28: ($4030.6) file ends prematurely during read 29: ($4030.6) file ends prematurely during write 30: ($4032.1) disk head has reached most inner track (end) *************************** *ROM BIOS disk subroutines* *************************** The following is a documentation on some of the most important ROM BIOS disk subroutines (including entry point addresses in the NES/FC memory map). This information is provided mostly to demonstrate the exact procedures the ROM BIOS follows during disk I/O transfers, since higher-level disk interface procedures (described later) are a much easier and more practical way of accessing disk data. A pseudo-code listing of the low-level events that occur in pretty much all of the procedures described is provided. The pseudo-code reproduces the I/O events *exactly* as the ROM BIOS code executes them in (even though some of the writes seem superfluous). Emulator authors and FDS low-level code developers should find this information especially useful. In the pseudo code, "x" is used to represent a bit that doesn't matter (during comparisons), or a bit that is not changed (during assignments). +-----+ |Delay| +-----+ Entry point: $E153 Y on call: delay in milliseconds description: a time delay generator. +--------+ |XferByte| +--------+ Entry point: $E7A3 A on call: byte to write to disk A on return: byte read from disk description: Waits for an IRQ to occur, then reads [$4031] & writes [$4024]. Only the current status of the write flag ($4025.2) determines if data is actually written to disk, or if valid data is read off the disk. Logic: (Wait for IRQ occurence) temp=[$4031] [$4024]=A A=temp return +-----+ |Error| +-----+ Entry point: $E781 X on call: error code A,X on return: error code Description: restores stack to a prior state before returning, and terminates data transfer. Logic: S = [$04]; restore stack frame to a previous state [$4025] = 0010x11x; disable scan disk bit A = X = ErrorCode return +----------+ |WaitForRdy| +----------+ Entry Point: $E64D Description: used to initilalize the disk drive for data transfers. Logic: [$4025] = 0010x110; stop motor (if it was already running) Delay(512) [$4025] = 0010x111; no effect [$4025] = 0010x101; scan disk Delay(150); allow pleanty of time for motor to turn on [$4026] = 1xxxxxxx; enable battery checking if ([$4033] = 0xxxxxxx);check battery good bit then Error($02) [$4025] = 0010x110; stop motor again [$4025] = 0010x111; no effect [$4025] = 0010x101; scan disk again repeat if ([$4032] = xxxxxxx1) then Error($01); constantly examine disk set until ([$4032] = xxxxxx0x);wait for ready flag to activate return +------------+ |CheckBlkType| +------------+ Entry point: $E68F A on call: expected block type Description: compares the first byte in a new data block to the one passed in A. Generates an error if test fails. Logic: Delay(5); advance 5 ms into GAP period [$4025] = x1xxxxxx; wait for block start mark to appear [$0101] = 01000000; set IRQ mode to disk byte transfer [$4025] = 1xxxxxxx; enable disk transfer IRQs if (XferByte <> BlockType);test first byte in block then Error($21+BlockType) return +------------+ |WriteBlkType| +------------+ Entry point: $E6B0 A on call: block type to create Description: creates a new data block, and writes the passed block type to it as the first byte. Logic: [$4025] = 00x0x0xx; set transfer direction to write Delay(10); create a 10 ms GAP period [$4024] = 00000000; zero out write data register [$4025] = 01x0x0xx; start writing data via $4024 to disk [$0101] = 01000000; set IRQ mode to disk byte transfer [$4025] = 1xxxxxxx; enable disk transfer IRQs XferByte($80); write out block start mark XferByte(BlockType); write out specified block type return +------------+ |EndOfBlkRead| +------------+ Entry point: $E706 Description: called when all (no more and no less) data from a block has been read in. Tests CRC bit to verify data block's integrity. Logic: XferByte; dummy read in CRC byte 1 if ([$4030] = x1xxxxxx) then Error($28) [$4025] = xxx1xxxx; activate process CRC bit XferByte; dummy read in CRC byte 2 if ([$4030] = xxx1xxxx);test (CRC accumulator = zero) status then Error($27) [$4025] = 00x0x1xx; disable further disk IRQ's, etc. if ([$4032] = xxxxxxx1);check disk set status then Error($01) return +-------------+ |EndOfBlkWrite| +-------------+ Entry point: $E729 Description: called when all data has been written to a block. Writes out 16-bit CRC value generated by the FDS hardware as last 2 bytes of file. Logic: XferByte; pipes last byte written into shift registers if ([$4030] = x1xxxxxx) then Error($29) [$4025] = xxx1xxxx; activate process CRC bit Delay(0.5); contents of CRC register written for 0.5 ms if ([$4032] = xxxxxx1x);check drive ready status then Error($30); disk head reached end of disk [$4025] = 00x0x1xx; disable further disk IRQ's, etc. if ([$4032] = xxxxxxx1);check disk set status then Error($01) return Disk block processing examples ------------------------------ (reading first block on disk) WaitForRdy; initalize drive & wait for ready flag Delay(267); advance 267 ms into first GAP period CheckBlkType(); wait for block start mark & confirm block type (where data can be read from disk) EndOfBlkRead (writing first block on disk *) WaitForRdy [$4025] = 00x0x0xx; set transfer direction to write Delay(398); create a 398 ms GAP period WriteBlkType(); 10 more ms of GAP, then write block type (where data can be written to disk) EndOfBlkWrite (reading subsequent blocks on disk) CheckBlkType() (where data can be read from disk) EndOfBlkRead (writing subsequent blocks on disk) WriteBlkType() (where data can be written to disk) EndOfBlkWrite (ending disk transfer, including if an error occurs) Error(); error # is set to 0 when disk xfer successful *: the ROM BIOS code does not provide any standard way of doing this. Games that must rewrite the first data block on a FDS disk should follow the example given here. The delay value listed is an approximation of the size that first GAP period on the disk should be. The figure is based on the size that GAP periods on typical FDS disks are (it seems to follow the figure 1.5x, where x is the time the ROM BIOS waits in the gap period during the reading of the first block). ************************ *ROM BIOS disk routines* ************************ These are the routines that FDS games use for disk access (presumably the only ones). They are called directly from FDS game code via JSR $xxxx instructions. The parameters that the routines work on are hardcoded into the instruction stream following the JSR $xxxx instruction. Each parameter is 16-bits, and one or two may be present. The called subroutines always fix the stack so that program control is returned to the instruction after the hardcoded parameters. - when one of these routines is called, the disk set status ($4032.0), and for routines that write to the disk, write protect status ($4032.2) are checked before starting the disk transfer. If these tests fail, a final error # is generated. If an error occurs during a disk transfer, a second attempt at the transfer is made before the error is considered to be final. - after any final error is generated, program control is returned to the instruction to follow the call, and the error code will be in A and X, with the sign and zero flags set to reflect the error #. - don't expect disk calls to return quick; it may take several seconds to complete. - the ROM BIOS always uses disk IRQ's to transfer data between the disk, so programs must surrender IRQ control to the ROM BIOS during these disk calls. The value at [$0101] however, is preserved on entry, and restored on exit. Parameters and procedures ------------------------- The types of structures that the disk routines work with will be described first, and then the disk routines themselves. +------------------------------------+ |Disk identify header string (DiskID)| +------------------------------------+ This is a commonly used string. It consists of 10 bytes which are all compared directly against bytes 15..24 (right after the '*NINTENDO-HVC*' string) of the disk's header block (block type 1; always the first one on the disk). If any of the bytes fail the comparison, an appropriate error # is generated. Comparisons of immaterial data can be skipped by placing an $FF byte in the appropriate place in the DiskID string (for example, when the ROM BIOS boots a disk, it sets all the fields in the DiskID string to -1, except disk side #, and disk #, which are set to 0 (so these fields have to match 0)). The following chart describes the DiskID structure, and the error #'s returned when a comparison fails. offset size error# description ------ ---- ------ ----------- 0 1 $04 game manufacturer code 1 4 $05 game ASCII name string 5 1 $06 game version 6 1 $07 disk side # 7 1 $08 disk # 8 1 $09 extra disk # data 9 1 $10 extra disk # data A - +-------------------------+ |File load list (LoadList)| +-------------------------+ This string is used when games need to specify which files to load from disk into memory. Each byte in LoadList specifies a file to load into memory. As file headers are sequentially read off the disk, the file identification code (FileID; byte offset 2 in block types 3) is compared to every element in the LoadList. If a match is found, the file is loaded. This is done until there are no more files on the disk (as indicated by the file count stored in block type 2 on the disk). The LoadList can be terminated by a $FF entry. Only the first 20 entries in the list will be processed (this is done because only about 800 clock cycles are available to the compare algorithm during this time). If $FF is the first element in the string, then this indicates that the boot load file code (BootID; stored on the disk in block 1, byte offset 25) is to be used for deciding which files to load off disk (in this case, the condition for loading a file is (BootID >= FileID)). +------------------------+ |File header (FileHeader)| +------------------------+ This structure is specified when a file is to be written to the disk. The first 14 bytes of this structure directly specify the data to use for generating a file header block (type 3, bytes [2..15]) to write to disk. The last 2 entries concern the file data to be written to disk (block type 4). The following is a table describing the FileHeader structure. offset size description ------ ---- ----------- 00 1 file ID code 01 8 file name 09 2 load address 0B 2 file data size 0D 1 load area (0 = CPU data; other = PPU) 0E 2 source address of file data (NOT written to disk) 10 1 source address type (0=CPU,other=PPU; NOT written to disk) 11 - +---------------------------+ |Disk information (DiskInfo)| +---------------------------+ This is a data structure returned by a subroutine, of collected information from the disk (list of files on disk, disk size, etc.). The following table is a description of that structure. offset size ------ ---- 0 1 game manufacturer code 1 4 game ASCII name string 5 1 game version 6 1 disk side # 7 1 disk # 8 1 extra disk # data 9 1 extra disk # data A 1 # of files on disk (the following block will appear for as many files as the "# of files on disk" byte indicates) B 1 file ID code C 8 file name (ASCII) (the following is present after the last file info block. Disk size is equal to the sum of each file's size entry, plus an extra 261 per file.) x 1 disk size high byte x+1 1 disk size low byte x+2 - +----------+ |Load Files| +----------+ Entry point: $E1F8 RETaddr: pointer to DiskID RETaddr+2: pointer to LoadList A on return: error code Y on return: count of files actually found Description: Loads files specified by DiskID into memory from disk. Load addresses are decided by the file's header. +-----------+ |Append File| +-----------+ entry point: $E237 RETaddr: pointer to DiskID RETaddr+2: pointer to FileHeader A on return: error code special error: #$26 if verification stage fails Description: appends the file data given by DiskID to the disk. This means that the file is tacked onto the end of the disk, and the disk file count is incremented. The file is then read back to verify the write. If an error occurs during verification, the disk's file count is decremented (logically hiding the written file). +----------+ |Write File| +----------+ Entry point: $E239 RETaddr: pointer to DiskID RETaddr+2: pointer to FileHeader A on call: file sequence # for file to write A on return: error code special error: #$26 if verification fails Description: same as "Append File", but instead of writing the file to the end of the disk, A specifies the sequential position on the disk to write the file (0 is the first). This also has the effect of setting the disk's file count to the A value, therefore logically hiding any other files that may reside after the written one. +--------------------+ |Get Disk Information| +--------------------+ Entry point: $E32A RETaddr: pointer to DiskInfo A on return: error code Description: fills DiskInfo up with data read off the current disk. +-----------------+ |Adjust File count| +-----------------+ Entry point: $E2BB RETaddr: pointer to DiskID A on call: number to reduce current file count by A on return: error code Special error: #$31 if A is less than the disk's file count Description: reads in disk's file count, decrements it by A, then writes the new value back. +--------------+ |Set File count| +--------------+ Entry point: $E2B7 RETaddr: pointer to DiskID A on call: number to set file count to A on return: error code Special error: #$31 if A is less than the disk's file count Description: reads in disk's file count, decrements it by A, discards the value, then sets the disk's file count to A. It should be noted here that there should be no good reason to use this subroutine over the next one. +-----------------------+ |Set File count (alt. 1)| +-----------------------+ Entry point: $E305 RETaddr: pointer to DiskID A on call: number to set file count to A on return: error code Description: sets the disk's file count to A. +-----------------------+ |Set File count (alt. 2)| +-----------------------+ entry point: $E301 RETaddr: pointer to DiskID A on call: number to set file count to minus 1 A on return: error code Description: sets the disk's file count to A+1. ************************************************ *ROM BIOS disk routine emulation considerations* ************************************************ For ambitious emulator authors, sporting a built-in FDS disk routine interface (and scrapping the disk-related ROM BIOS code alltogether), is a win-win situation. First, since hard drive disk access is lightning fast (even faster if the FDS disk data is cached), disk routines may take no time to execute (less than a frame). This means that FDS games will have NO noticible load/save times! Secondly, when disk access is requested by a game, the proper side & disk # are specified. these parameters can be examined, and the matching FDS disk image can be selected automatically (no more pissing around with side A,B,C,D,...)! For the less ambitious emulator author who doesn't want to write their own FDS disk call emulator, but still would like to see a decrease in load/save times, here's an idea. The ROM BIOS disk subroutines call a wait routine (waiting for an IRQ) whenever a byte is to be transfered to/from the disk. The solution to this is to change the wait loop branch target so that it branches directly to the IRQ handler for reading/writing disk data. This way, data is accessed as fast as the 6502 emulation will allow. Another way of decreasing the load/save times is by not limiting the # of 6502 clock cycles per frame that occur during the disk call. EOF