Sie sind auf Seite 1von 91

***************************************** *Famicom Disk System technical reference* ***************************************** Brad Taylor (BTTDgroup@hotmail.

com) 3rd release: April 23rd, 2004 Thanks to the NES community. http://nesdev.parodius.com. Special thanks to Nori and Goroh for thier existing related docs (and Ki and Sgt. Bowhack for their respective translations), and to the one known as "D" for his preliminary ROM BIOS documentation he posted on NESdev, a long time ago. Recommended literature: Nintendo's patent document on the operation of the FDS's integrated electronic sound synthesizer (U.S.#4,783,812); this device is not covered in this document. Note: to display this document properly, your text viewer needs two things: 1. support for the classic VGA-based text mode 256 character set with line-drawing characters. 2. word-wrap. windows notepad can easially do both if you change the font over to terminal style. +----------------------------+ |What this document describes| +----------------------------+ - preface - general information on the RAM adaptor hardware - 2C33/LH2833 pin nomenclature & signal descriptions - 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 - brief description of FDS Disk drive operation - the FDS data transfer protocol - FDS disk magnetic encoding format - low level disk data storage layout - CRC calculations for files on FDS disks - RAM adaptor pinouts & their function - Steps to emulating the disk drive - Getting FDS disk games to boot that normally don't - Circumventing copy protection - ROM BIOS disassembly ********* *Preface* ********* Even when considering the limitations of the hardware (and it's short, 2-year product cycle), it's not very hard to understand the reason why the Famicom Disk System continues to impress all who see it in action for the first time; it turns the NES/FC into a real personal computing platform. Using floppy disks to boot the system 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. For gamers who had never seen the FDS versions of games like Kid Icarus, Metroid, and Castlevania, these individuals are usually very impressed with the fact that all these games were released originally on the FDS with save features, opposed to the password system their cart counterparts had (except Castlevania- Konami was cheap, and

scrapped *all* support for progress saving in the cart version. 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. The "FDS loader" project was inspired by my desire to play all kinds of FDS games off the internet on my home-made stereo AV famicom console. So, I quickly became interested in figuring out how FDS communication was being accomplished there, since at the time, I had no knowledge of any existing english-written FDS technical reference documents explaining how this was accomplished. In the end, the project was completed, everything turned out okay, and I was satisfied for a while. 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 all together. 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 as well. 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 in 2002: studying the FDS's ROM BIOS code, and figuring out how the hell games access the disk subroutines. Needless to say, that goal has been realized, and documented here. In this addition (2004), information on the technical operation of an FDS disk drive, ROM BIOS disassembly, pinouts of 2C33 & LH2833 chips documented, plus an FDS disk fixing technique, and copy protection circumvention indformation have all been added here to integrate/combine all my FDS-related research findings to date, into this single document. ******************************* *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 (part number: LH2833; built by Sharp) (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 and addressing, since only a few system address lines are directly connected 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. **************************************************** *2C33/LH2833 pin nomenclature & signal descriptions* **************************************************** ___ ___ |* \/ | /PRG >01] [64< VCC A14 >02] [63] XTAL A13 >03] [62] XTAL A12 >04] [61< VEE A11 >05] [60] (02) A10 >06] [59] (09) A9 >07] [58] (08) A8 >08] [57< R/W (22) [09] [56< PHI2 (23) [10] [55> /IRQ (24) [11] [54> AOUT (04) [12] [53< VEE (05) [13] [52> SER OUT (06) [14] [51< SER IN (03) [15] [50> $4025W.2 A7 >16] 2C33 [49> $4025W.1 A6 >17] [48> $4025W.0 A5 >18] [47< $4032R.2 A4 >19] [46< $4032R.1 A3 >20] [45< $4032R.0 A2 >21] [44] EXT0 A1 >22] [43] EXT1 A0 >23] [42] EXT2 -- [24] [41] EXT3 D0 [25] [40] EXT4 D1 [26] [39] EXT5 D2 [27] [38] EXT6 D3 [28] [37] EXT7/BATT D4 [29] [36< CHR A10 D5 [30] [35< CHR A11 D6 [31] [34> VRAM A10 VEE >32] [33] D7 |________|

PHI2 (60) (15) (12) (13) (14) A7 (58) (59) -D5 D6 D7 VEE

___ ___ |* \/ | >01] [28< [02] [27< [03] [26< [04] [25< [05] [24] [06] [23] >07] [22] [08] LH2833 [21< [09] [20< [10] [19] [11] [18] [12] [17] [13] [16] >14] [15] |________|

VCC A13 A14 /PRG (11) (10) (09) R/W VEE D4 D3 D2 D1 D0

/PRG: this is the NES/FC's A15 line NAND gated with the PHI2 line. A0-A14, D0-D7, R/W, PHI2, /IRQ: the NES's 6502 address, data, control, and interrupt lines (see "2A03 technical reference" doc for details). XTAL: leads to drop a 21.48 MHz crystal across, in order for the 2C33 to function. Note that this frequency is exactly the same as the one clocking the 2A03 & 2C02 chips in NTSC-based NES consoles. VEE, VCC: ground, and +5VDC power signals, respectfully. EXT0-EXT7: these bidirectional pins relate to the contents of internal registers mapped in at addresses $4026 & $4033 (described in detail later). $4025W.x: outputs fed directly off an internal latch mapped in at that address. $4032R.x: inputs that effect the value that this port returns when accessed. SER IN, SER OUT: serial input and output signals (FDS disk data signals). CHR A10-A11, VRAM A10: PPU mirroring control. AOUT: the analog output of the 2C33's internal sound synthesizer. (): numbers listed inside parenthesis indicate private pin connections between the 2C33 and the LH2833 chips. Though undocumented, these signals have to be responsible for maintaining the LH2833's DRAM timing. --: unused/unconnected. *********************************** *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 FDS-related 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 --0: description ----------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 --0 1 2 3 4 5 6 7 2C33 ---44 43 42 41 40 39 38 37 ext.con ------3 4 5 6 7 8 9 -

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 --0: description ----------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 passe d the CRC). 6: 7: Unclear operation. Prehaps relates to $4032.1. 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 --0: description ----------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 ran 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): ($DFFC): ($DFFA): ($DFF8): ($DFF6): ($0102): [$0101]: [$0100]: [$FF]: [$FE]: [$FD]: [$FC]: [$FB]: [$FA]: [$F9]: disk disk disk disk disk game game game game game IRQ vector reset vector NMI vector #3 NMI vector #2 NMI vector #1 (if (if (if (if (if [$0101] ($0102) [$0100] [$0100] [$0100] = = = = = 11xxxxxxB) $5335, or $AC35) 11xxxxxxB) 10xxxxxxB) 01xxxxxxB)

PC action on reset PC action on IRQ. set to $80 on reset PC action on NMI. set to $C0 on reset value value value value value value value last last last last last last last written written written written written written written to to to to to to to [$2000] [$2001] [$2005]#1 [$2005]#2 [$4016] [$4025] [$4026] $80 $06 0'd 0'd 0'd $2E $FF on on on on on on on reset. reset reset. reset. reset. reset. 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) ($02) [$04] [$05] [$06] [$07] [$08] [$09] ($0A) ($0C) [$0E] first hardcoded parameter second hardcoded parameter previous stack frame error retry count file counter current block type boot ID code not 0 to make dummy reads destination address byte xfer count 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: 01: 02: 03: 21: 22: 23: 24: 25: 27: 28: 29: 30: disk I/O successful (no error) ($4032.0) disk not set ($4033.7) power supply failure (i.e., battery) ($4032.2) disk is write protected '*NINTENDO-HVC*' string in block 1 doesn't match block type 1 expected block type 2 expected block type 3 expected block type 4 expected ($4030.4) block failed CRC ($4030.6) file ends prematurely during read ($4030.6) file ends prematurely during write ($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: Y on call: description:

$E153 delay in milliseconds 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: Description: Logic:

$E64D used to initilalize the disk drive for data transfers.

[$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; Delay(267); CheckBlkType(); (where data can be read EndOfBlkRead

initalize drive & wait for ready flag advance 267 ms into first GAP period wait for block start mark & confirm block type from disk)

(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 -----0 1 5 6 7 8 9 A size ---1 4 1 1 1 1 1 error# -----$04 $05 $06 $07 $08 $09 $10 description ----------game manufacturer code game ASCII name string game version disk side # disk # extra disk # data extra disk # data -

+-------------------------+ |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 an $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 -----00 01 09 0B 0D 0E 10 11 size ---1 8 2 2 1 2 1 description ----------file ID code file name load address file data size load area (0 = CPU data; other = PPU) source address of file data (NOT written to disk) source address type (0=CPU,other=PPU; NOT written to disk) -

+---------------------------+ |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 -----0 1 5 6 7 8 9 A

size ---1 4 1 1 1 1 1 1

game manufacturer code game ASCII name string game version disk side # disk # extra disk # data extra disk # data # of files on disk

(the following block will appear for as many files as the "# of files on disk" byte indicates) B C 1 8 file ID code 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 x+1 x+2 1 1 disk size high byte disk size low byte -

+----------+ |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: RETaddr: RETaddr+2: A on call:

$E239 pointer to DiskID pointer to FileHeader 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. +----------------+ |Check 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, compares it to A, then sets the disk's file count to A. +-----------------------+ |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 NES/FC emulator authors, sporting a built-in FDS disk routine interface (and scrapping the disk-related ROM BIOS code alltogether), can make FDS-written games run on an NES emulator as fast as cart/ROM based games. This means that you will probably never even notice a single save/load screen while playing any FDS game you can think of, other than the regular change side prompts. For emulators which base their NES 6502 emulation address decoding on a full-sized 17-bit address look-up table, trapping access to FDS routines is a snap. Other emulators may have to use 6502 jam opcodes at the target addresses of a disk routine in the ROM BIOS, to tell your 6502 engine to transfer control to another emulator routine handling the FDS disk request. Once access to these routines are trapped, you would write your handlers to perform the same task as the ROM BIOS code, except without having to deal with using a virtual 2C33 engine to access disk data. Also, CPU cycle count information is irrelivant, so there's no point in worrying about it. Since you'll likely have the FDS disk data cached somewhere in your emulator's heap, most the work your disk routine has to do will consist of copying a few thousand bytes from FDS disk cache memory, into the NES's memory map. Another bonus of emulating disk routines, is that when disk access is requested by a game, the proper side & disk # are specified along with the call. These parameters can be examined, and the matching FDS disk image can be selected, without ever having to prompt the user on which side, (or even the game disk) to choose. The only prompts that the user will have to deal with, are ones that ask the user to change the disk. Since disk side and game type is irrelivant because the game specifies that info, all the user has to do is push a single button to get the FDS emulator to continue the game's action again. 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 a suggestion. 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. *********************************************** *Brief description of FDS Disk drive operation* *********************************************** Data coming out of the FDS disk drive is a real time representation of what data the head is currently reading. This means that no formatting of the data occurs. The head is always advancing across the disk at a constant rate (sequential access) as the disk spins. When the head reaches the end of the disk (most inner track), it returns to the beginning of the disk (most outer track) and the cycle repeats, upon request from the RAM adaptor. This means that on every scan, the entire disk is read (which takes about 6 seconds). The disk drive signals the RAM adaptor when the head has been positioned to the outer most track, and is starting a new scan.

**************************** *FDS data transfer protocol* **************************** In any data transfer, 2 pieces of information (signals) must be sent: - a signal that represents the rate at which the data is being sent - the actual data Like most disk drive units, the FDS disk drive is sending it's data out via serial connection (1 wire, meaning 1 bit at a time). Data prerecorded on FDS disks have already had the 2 aformentioned signals "combined" using an XOR algorithm described below (note that I've used underscores (_) and hyphens (-) to respectfully represent 0 and 1 logic levels in my diagram below). Rate Data XOR Disk ----____----____----____----____----____----____----____----____----____ ----------------________________________--------________---------------____----____--------____----____----________--------________----____---____-_______-___________-_______-___________-_______________-_______-___ time ---> Rate is the signal that represents the intervals at which data (bits) are transfered. A single data bit is transfered on every 0 to 1 (_-) transition of this signal. The RAM adaptor expects a transfer rate of 96.4kHz, although the tolerance it has for this rate is 10%. This tolerance is neccessary since, the disk drive can NOT turn the disk at a constant speed. Even though it may seem constant, there is a small varying degree in rotation speed, due to it's physical architecture. Data is the desired sequential binary data to be serialized. I specifically chose the data used here to demonstrate how it would be stored on a FDS disk. Note that if data changes, it changes syncronously with the 0 to 1 (_-) transition of the Rate signal. XOR represents the result of a logical exclusive OR performed on the Rate and Data signals. Disk represents what is ACTUALLY recorded on a FDS disk (and therefore, what is coming out of the disk drive, and going to the RAM adaptor). This signal is constructed of pulses that are written in sync with every 0 to 1 (_-) transition of the XOR result signal. In reality, the length of these pulses (the time it stays as 1) is about 1 microsecond. When the RAM adaptor sends data out to write to the disk, it is also in this format (although logically inverted compared to the diagram shown above). However, data sent to the RAM adaptor is not required to be in this format. The RAM adaptor responds to the 0 to 1 (_-) transitions in the serial data signal rather than the length of the pulses. This is why the RAM adaptor will interpret both serial data types (XOR or Disk) indifferently. *********************************** *FDS disk magnetic encoding format*

*********************************** The system for magnetically storing data on FDS disks is very simple. At any instant, when the disk head is reading the disk, it has no way of telling what magnetic charge the current section of disk area has (consequently, the head produces no voltage). Because of how the physics of magnetic inductance works, the head will *only* generate voltage while a section of the disk track containing opposite charges is being read. During this time, the head produces a spike in voltage (the duration of the spike is a function of the width of the head (the area physically contacting the disk's surface), and the rotation speed of the disk). These spikes are then amplified, and sent to the data out pin on the drive. This is how data encoding on FDS disks is made. The following diagram should provide more explanation: POL DATA time _____ _____ _____ \_____/ \_____/ \_____/ -_____-_____-_____-_____-_____-_____ --->

POL represents the polarity of the magnetic charge on a typical disk track. DATA is the interpretation of the POL changes, and is also the digital signal that appears on the "data out" signal of the drive as a result. +-------------------------------------+ |Magnetically recording data onto disk| +-------------------------------------+ Data entering the disk drive intended to be written onto the disk is not written directly. Like the RAM adaptor, the data input on the disk drive responds to the positive-going edge of pulses fed into it. This is because the data is fed into an internal positive-edge triggered toggle flip-flop (divide-by-2 counter). The output status of this flip-flop is then the signal that is directed to the drive's write head during the time disk writing is to be engaged. The 2 write head wires are connected to the flip-flop's complimentary outputs, so that they are always in opposite states during the writing phase. During writing, the current that flows through the write head (in either direction) is about 20 to 25 milliamperes. The following chart outlines the relationship between these signals: Din W1 W2 time ----__--__--____--__----______--____---______----______----__________------____ ------____------____----------______------>

Din is the data entering the drive, intended to be written to disk. W1 & W2 represent the digital status of the wires connected to the disk drive's write head. ************************************

*Low level disk data layout/storage* ************************************ Now that we have covered the communication protocol used, let's talk about the layout of data structures on the disks. Nori's document has done a good job of providing information on the way RAW data is layed out on the disk. At this point, I recommend referring to Nori's "fds-nori.txt" and reading the first half of the document describing the disk data structures. Here, I would like to elaborate on some items found in the aforementioned document. Note that any references to bit or byte data transfers assume the standard 96.4kHz xfer bit rate (meaning 1 bit is transfered in 1/96400 seconds). - The disk drive unit signals the RAM adaptor when the head has moved to the beginning of the disk via the "-ready" signal it sends out (more on this later). The "-ready" signal is based on a mechanical switch inside the drive which is activated when the head is brought back to the outer most edge of the disk (the beginning). Because the switch will usually be triggered prematurely, the first 13000 bits (approx.) of data the drive will send out immediately after this switch is activated will be invalid. To compensate for this, the RAM adaptor purposely ignores the first 26100 bits (approx.) sent to it since it recieves the "-ready" signal from the disk drive. - After this period, the RAM adaptor is going to be interpreting data read off the disk. Because of the many mechanical variables these disk drive units have, the data recorded on the disk cannot anticipate the exact point at which the RAM adaptor will start accepting data. Therefore, a padding mechanism is used, which is reffered to here as the "GAP" period. - All GAP periods recorded on the disk are composed entirely of binary coded 0's. The RAM adaptor ignores these 0's (meaning that there is no size limit to any GAP period recorded on the disk) until a set (1) bit is recieved, which identifies the start of a file (reffered to in Nori's doc. as the "block start mark"). The data immediately following this bit makes up the file data. Size of the file is then determined by the file data read in (consult Nori's doc). Note that this is the only way to determine the size of a file, since there really is no mechanism implemented (like there is for the start of a file) to indicate where a file's data ends. - The order serial binary data is recorded on the disk in is the little endian format. Lesser significant bits are sent first before greater, and same for bytes. - The length of the first GAP period present on typical FDS disks (relative to the instant the disk drive's "-ready" signal is activated) is about 40000 bits, after which the first block start mark (indicating the beginning of the first file) will appear. - Writing to the disk does not occur in sync with the existing data on the disk. Where a write period starts and ends is bad news because it creates a gap between pulses almost always of an invalid length. This would lead to the RAM adaptor misinterpreting the data, causing errors. To overcome this, the RAM adaptor always ignores the first 488 bits (aprox.) to follow after the immediate end of any file. This period allows the RAM adaptor (or the game rather) an oppertunity to make the switch from reading from the disk to writing or vice-versa. - After this period, the adaptor will expect another gap period, which will then lead into a block start mark bit, and the next file. This cycle repeats until there are no more files to be placed on a disk.

- The typical GAP period size used between files on FDS disks is roughly 976 bits (this includes the bits that are ignored by the RAM adaptor). - The rest of the disk is filled with 0's after the last file is recorded (although it really shouldn't matter what exists on the disk after this). ****************** *CRC calculations* ****************** Special thanks to Val Blant for assistance in cracking the CRC algorithm used by the FDS. The "block end mark" is a CRC calculation appended to the immediate end of every file. It's calculation is based exclusively on that files' data. The CRC is 16-bits, and is generated with a 17 bit poly. The poly used is 10001000000100001b (the X25 standard). Right shift operations are used to calculate the CRC (this effectively reverses the bit order of the polynomial, resulting in the 16-bit poly of 8408h). A x86 example is shown below (note that ; is used to seperate multiple statements on one line, and // is used for comments). The file this algorithm is designed to work on has no block start mark in it ($80), and has 2 extra bytes at the end (where a CRC calculation would normally reside) which are 0'd. While the block start mark is actually used in the calculation of a FDS file CRC, you'll see in the algo below that the block start mark q'ty ($80) is moved directly into a register. // ax is used as CRC accumulator // si is the array element counter // di is a temp reg // Size is the size of the file + 2 (with the last 2 bytes as 0) // Buf points to the file data (with the 2 appended bytes) mov sub @1: mov inc shr shr shr shr shr shr shr shr cmp jc ax,8000h si,si // this is the block start mark // zero out file byte index ptr

dl,byte ptr Buf[si] si dl,1; dl,1; dl,1; dl,1; dl,1; dl,1; dl,1; dl,1; rcr rcr rcr rcr rcr rcr rcr rcr ax,1; ax,1; ax,1; ax,1; ax,1; ax,1; ax,1; ax,1; sbb sbb sbb sbb sbb sbb sbb sbb di,di; di,di; di,di; di,di; di,di; di,di; di,di; di,di; and and and and and and and and di,8408h; di,8408h; di,8408h; di,8408h; di,8408h; di,8408h; di,8408h; di,8408h; xor xor xor xor xor xor xor xor ax,di ax,di ax,di ax,di ax,di ax,di ax,di ax,di

si,Size @1

// ax now contains the CRC. Of course, this example is only used to show how the CRC algorithm works. Using a precalculated CRC look-up table is a much faster (~5 cc/it) method to calculate CRCs in the loop (the above method consumes over 40 clock cycles per iteration). However, if speed is not an issue, the above code

uses only a fraction of the memory a table look-up implementation would consume. More memory can be saved by loading the polynomial value into a register, and even more by rolling the repeated instructions up into a second loop. ********************************* *FDS RAM adaptor control signals* ********************************* Special thanks to Christopher Cox for additional FDS wiring information not originally here. 1 3 5 7 9 B 2 4 6 8 A C Open-end view of the RAM adaptor's disk drive connector. pin # ----1 2 3 4 5 6 7 8 9 A B C *2C33 pin --------50 64 49 32 52 37 47 51 45 46 48 *RAM pins --------5 (green) C (cyan) 6 (blue) 1 (brown) 3 (orange) B (pink) 8 (grey) 4 (yellow) A (black) 9 (white) 7 (violet) signal description -----------------(O) -write (O) VCC (+5VDC) (O) -scan media (O) VEE (ground) (O) write data (I) motor on/battery good (I) -writable media (I) motor power (note 1) (I) read data (I) -media set (I) -ready (O) -stop motor

notes on symbols ---------------(O): Signal output. (I): Signal input. - : Indicates a signal which is active on a low (0) condition. * : These are corresponding pinouts for the 2C33 I/O chip, and the other end of the RAM adaptor cable, which both are located inside the RAM adaptor. 1 : The RAM adaptor does not use this signal (there is no wire in the cable to carry the signal). An electronically controlled 5-volt power supply inside the disk drive unit generates the power that appears here. This power is also shared with the drive's internal electric motor. Therefore, the motor only comes on when there is voltage on this pin. (O) -write ---------While active, this signal indicates that data appearing on the "write data" signal pin is to be written to the storage media.

(O) write data -------------This is the serial data the RAM adaptor issues to be written to the storage media on the "-write" condition. (O) -scan media --------------While inactive, this instructs the storage media pointer to be reset (and stay reset) at the beginning of the media. When active, the media pointer is to be advanced at a constant rate, and data progressively transferred to/from the media (via the media pointer). (O) -stop motor --------------Applicable mostly to the FDS disk drive unit only, the falling edge of this signal would instruct the drive to stop the current scan of the disk. (I) motor on/battery good ------------------------Applicable mostly to the FDS disk drive unit only, after the RAM adaptor issues a "-scan media" signal, it will check the status of this input to see if the disk drive motor has turned on. If this input is found to be inactive, the RAM adaptor interprets this as the disk drive's batteries having failed. Essentially, this signal's operation is identical to the above mentioned "motor power" signal, except that this is a TTL signal version of it. (I) -writable media ------------------When active, this signal indicates to the RAM adaptor that the current media is not write protected. (I) read data ------------when "-scan media" is active, data that is progressively read off the storage media (via the media pointer) is expected to appear here. (I) -media set -------------When active, this signal indicates the presence of valid storage media. (I) -ready ---------Applicable mostly to the FDS disk drive unit only, the falling edge of this signal would indicate to the RAM adaptor that the disk drive has acknowledged the "-scan media" signal, and the disk drive head is currently at the beginning of the disk (most outer track). While this signal remains active, this indicates that the disk head is advancing across the disk's surface, and apropriate data can be transferred to/from the disk. This signal would then go inactive if the head advances to the end of the disk (most inner track), or the "-scan media" signal goes inactive. *********************************** *Steps to emulating the disk drive* *********************************** Before a data transfer between the RAM adaptor and the disk drive/storage media can commence, several control signals are evaluated and/or dispatched. The order in which events occur, is as follows.

1. "-media set" will be examined before any other signals. Activate this input when your storage media is present. Make sure this input remains active throughout the transfer (and for a short time afterwards as well), otherwise the FDS BIOS will report error #1 (disk set). If this signal is not active, and a data transfer is requested, the BIOS will wait until this signal does go active before continuing sending/examining control signals. 2. If the RAM adaptor is going to attempt writing to the media during the transfer, make sure to activate the "-writable media" input, otherwise the FDS BIOS will report error #3 (disk write protected). Note that on a real disk drive, "-writable media" will active at the same time "-media set" does (which is when a non-write protected disk is inserted into the drive). A few FDS games rely on this, therefore for writable disks, the "-write enable" flag should be activated simultaniously with "-media set". 3. The RAM adaptor will set "-scan media"=0 and "-stop motor"=1 to instruct the storage media to prepare for the transfer. This will only happen if the first 2 conditions are satisfied. 4. "motor on/battery good" will be examined next. Always keep this input active, otherwise the FDS BIOS will report error #2 (battery low). 5. Activating "-ready" will inform the RAM adaptor that the media pointer is currently positioned at the beginning of the media, and is progressing. It is during the time that this signal is active that media data transfers/exchanges are made. Make sure this input remains active during the transfer, otherwise the FDS BIOS will report an error. "-ready" shouldn't be activated until at least 14354 bit transfers (~0.15 seconds) have elapsed relative to step #3. 6. During the transfer, the "-write" signal from the RAM adaptor indicates the data transfer direction. When inactive, data read off of the media is to appear on the "read data" signal. When active, data appearing on the "write data" signal is to be recorded onto the media. 7. The RAM adaptor terminates the data transfer at it's discretion (when it has read enough or all the files off of the media). This is done when "-scan media"=1 or "-stop motor=0". After this, it is OK to deactivate the "-ready" signal, and halt all media I/O operations. +-----------------+ |A few final notes| +-----------------+ - It is okay to tie "-ready" up to the "-scan media" signal, if the media needs no time to prepare for a data xfer after "-scan media" is activated. Don't try to tie "-ready" active all the time- while this will work for 95% of the disk games i've tested, some will not load unless "-ready" is disabled after a xfer. - Some unlicenced FDS games activate the "-stop motor" signal (and possibly even "-write", even though the storage media is not intended to be written to) when a media transfer is to be discontinued, while "-scan media" is still active. While this is an unorthodoxed method of doing this, the best way to handle this situation is to give the "-stop motor" signal priority over any others, and force data transfer termination during it's activation. - Check out my FDS loader project (which uploads *.FDS files to the RAM adaptor) for source code to my working disk drive emulator.

**************************************************** *Getting FDS disk games to boot that normally don't* **************************************************** After finishing the FDS loader project, I discovered a way to get FDS games that normally don't work to now boot up successfully. The trick to doing this lies in a part on the disk drive mechanism itself. When you insert a disk into the drive, the disk bay lowers down, and eventually the bottom side of the magnetic floppy contacts the head (the head is at a constant alditude). On the top side of the magnetic floppy, some kind of arm with a brush on it lowers onto the floppy's surface, and applies pressure on the floppy against the head, to allow for closer (or better) contact with it. I have discovered that by applying some extra force on this brush arm, disk games that regularly do not boot up, will now work. However, do not apply too much force, otherwise the friction from the brush will slow down the disk's RPM, and the RAM adaptor will not be able to boot from it anyway, since the transfer rate will be out of range. The permanent fix to this is to increase the tension that the spring already in there applies. To remove the spring, you must pop out that pin that it's wrapped around (which is also the pin that supports the arm as well). With the front of the drive facing towards you, chissel the pin from the right side of it to pop it out. It is neccessary to re-torque the spring, since simply adding an extra revolution to the windings will offer too much tension. Use both your hands (you may need to use needle-nosed plyers) to twist the spring in the SAME direction in that it is supposed to be applying pressure in. This will increase the size of the radius of the spring's coil. Don't overdue the re-torquing; hand strength is all you need to do it. After this, the spring is ready to be put back into the drive. It will be a little tricky to put the spring back onto the pin (with the arm kind of in the way), so this requires patience. If putting it back in seems easy, this means that you're not adding enough revolutions to the windings of the spring for force. At any rate, make sure that the force applied after you put the spring back in is a good deal more than when you removed it. For an easier way of incerasing the pressure the brush arm applies against the floppy without having to adjust/replace the arm's spring, I'd try taping some weight onto the arm (for example, a few pennies or dimes would make up the weight well). Personally, I tried this before re-torquing the spring, and it didn't work out very well (mostly because the tape was always brushing against somthing). As for why certain games work with the default pressure, while others require more, I would speculate that the surface of the disks in question are rippled, possibly left that way after years of use. Without enough pressure, the ripples are the only thing the head makes contact with, which is only a fraction of the area it needs to contact in order to read the magnetic data reliably. But don't take my word for it- just take a look at the surfaces of FDS disks that boot, and compare it to ones that don't. You'll find that working disks have a uniform surface, while the others will have tracks of wear on them, with most of the wear appearing at the end of the disk (the most inner tracks). The wear appears at the end because this is where the disk head is put to rest after every disk scan.

******************************* *Circumventing copy protection* ******************************* Hardware disk copy protection ----------------------------Apparently, Nintendo had designed FDS disk drive units so that they cannot reprogram entire disks, while still somehow being able to write the contents of individual files to the end of disks. Now, there's alot of undocumented things going on inside the disk drive unit, so I'm just going to say that there are two evil IC's you've got to watch out for inside the FDS disk drive- the 3213, and the 3206. There is a collection of 6 "FDS-COPY" jpegs over at NESdev which (pg. 4 right side, and pg. 5) give a pretty graphic overview of the steps involved in modding a stock FDS disk drive, so that it may reprogram disks. Although I haven't built the specific circuit described in the jpegs, I had designed & built a similar working circuit to defeat the FDS's evil copy protection circuitry, with excellent results. Software disk copy protection ----------------------------Special thanks to Chris Covell for bringing this to my attention. Apparently, some FDS disks implement a very simple copy protection scheme, which the game relies on in order for the game to refuse to work on the copied disk. Normally, the number of files that exist on an FDS disk is stored in the second block recorded on it. However, some games maintain "invisible" files, which are basically files that exist beyond what the file count number in the file count block indicates. This poses somewhat of a problem for copy software like FDSLOADR, since these tools rely on the file count block, and don't assume that there is any valid data past the last file found on the disk. This means that when these types of disks are copied, the invisible files will be lost, and when the game loads the files that do exist, the game's going to give the user heat about there being a file missing or somthing, gumming up the works. However in practice, when an FDS disk is programmed, the unused end of the disk is usually completely zeroed out, and this makes detecting the end of the disk simple: just wait to find a GAP period of extreme length. Except in rare cases, this model for detecting the true end of an FDS disk should generally provide the best results for copying the complete contents for all types of FDS disks. Physical disk lockout mechanism ------------------------------Ever wonder why Nintendo engraved their company's name along the handle edge of all FDS disks? Inside the FDS disk drive bay, sitting just behind the lower part of the front black plastic faceplate, is a little plastic block with the letters "Nintendo" carved out of a hard plastic block. This basically forces disks that don't have holes in those locations from completely loading into the drive, circumventing usage. Now while many companies made FDS disks with those holes cut out, I'm sure there must be some disks out there that are compatable with the FDS, but don't have the holes. So, the solution is to simply disassemble the FDS disk drive, remove the disk cage, and remove the two screws securing the "Nintendo" letterblock. **********************

*ROM BIOS disassembly* ********************** Special thanks to Tennessee Carmel-Veilleux for DCC6502, a 6502 code disassembler he wrote, which I used for this disassembly here. notes about disassembly ----------------------- tables like the one below display the bytes (hexidecimal pairs) as they would appear in the actual ROM (big endian). only multi-byte numbers preceeded by a "$" are stored in little endian. - a few suboutines in this code work with hard-coded parameters (this is data stored in the instruction stream following the JSR instruction). For subroutines that use hard-coded parameters, you will see a JSR $xxxx, followed by a comma, and the actual hardcoded (immediate) data. The subroutines of course adjust the return address to make sure program control returns to the address _after_ the hard-coded parameters. - not all the code has been commented, but I have gone over every single disk I/O related subroutine (which I could) find with a fine-toothed comb, and have (hopefully) made adequate comments for easier comprehension of their operation. ; ; FDS ROM BIOS disassembly ; $E000 DB 00 ;FONT BITMAPS 384CC6C6C66438001838181818187E00 7CC60E3C78E0FE007E0C183C06C67C00 1C3C6CCCFE0C0C00FCC0FC0606C67C00 3C60C0FCC6C67C00FEC60C1830303000 7CC6C67CC6C67C007CC6C67E060C7800 386CC6C6FEC6C600FCC6C6FCC6C6FC00 3C66C0C0C0663C00F8CCC6C6C6CCF800 FEC0C0FCC0C0FE00FEC0C0FCC0C0C000 3E60C0DEC6667E00C6C6C6FEC6C6C600 7E18181818187E001E060606C6C67C00 C6CCD8F0F8DCCE006060606060607E00 C6EEFEFED6C6C600C6E6F6FEDECEC600 7CC6C6C6C6C67C00FCC6C6C6FCC0C000 7CC6C6C6DECC7A00FCC6C6CEF8DCCE00 78CCC07C06C67C007E18181818181800 C6C6C6C6C6C67C00C6C6C6EE7C381000 C6C6D6FEFEEEC600C6EE7C387CEEC600 6666663C18181800FE0E1C3870E0FE00 00000000000000000000000030302000 0000000030300000000000006C6C0800 3844BAAAB2AA4438 ;131 clock cycle delay Delay131: PHA $E14A LDA #$16 $E14C SEC $E14D SBC #$01

$E14F $E151 $E152

BCS $E14D PLA RTS

;millisecond delay timer. Delay in clock cycles is: 1790*Y+5. MilSecTimer: LDX $00 $E155 LDX #$fe $E157 NOP $E158 DEX $E159 BNE $E157 $E15B CMP $00 $E15D DEY $E15E BNE MilSecTimer $E160 RTS ;disable playfield & objects DisPfOBJ: LDA $FE $E163 AND #$e7 $E165 STA $FE $E167 STA $2001; [NES] PPU setup #2 $E16A RTS ;enable playfield & objects EnPfOBJ: LDA $FE $E16D ORA #$18 $E16F BNE $E165 ;disable objects DisOBJs: LDA $FE $E173 AND #$ef $E175 JMP $E165 ;enable EnOBJs: $E17A $E17C objects LDA $FE ORA #$10 BNE $E165

;disable playfield DisPF: LDA $FE $E180 AND #$f7 $E182 JMP $E165 ;enable EnPF: $E187 $E189 playfield LDA $FE ORA #$08 BNE $E165

; ;NMI program control ; ;this routine controls what action occurs on a NMI, based on [$0100]. ;[$0100] ;------;00xxxxxx: ;01xxxxxx: ;10xxxxxx: ;11xxxxxx: program control on NMI ---------------------VINTwait was called; return PC to address that called VINTwait use [$DFF6] vector use [$DFF8] vector use [$DFFA] vector

;NMI branch NMI: BIT $E18E BPL $E190 BVC $E192 JMP $E195 JMP $E198 BVC $E19A JMP

target $0100 $E198 $E195 ($DFFA); ($DFF8); $E19D ($DFF6);

11xxxxxx 10xxxxxx 01xxxxxx

;disable further VINTs 00xxxxxx $E19D LDA $FF $E19F AND #$7f $E1A1 STA $FF $E1A3 STA $2000; [NES] PPU setup #1 $E1A6 LDA $2002; [NES] PPU status ;discard interrupted return address (should be $E1C5) $E1A9 PLA $E1AA PLA $E1AB PLA ;restore byte at [$0100] $E1AC PLA $E1AD STA $0100 ;restore A $E1B0 PLA $E1B1 RTS ;---------------------------------------------------------------------------;wait for VINT VINTwait: PHA; save A $E1B3 LDA $0100 $E1B6 PHA; save old NMI pgm ctrl byte $E1B7 LDA #$00 $E1B9 STA $0100; set NMI pgm ctrl byte to 0 ;enable $E1BC $E1BE $E1C0 $E1C2 VINT LDA $FF ORA #$80 STA $FF STA $2000;

[NES] PPU setup #1

;infinite loop $E1C5 BNE $E1C5 ; ;IRQ program control ; ;this routine controls what action occurs on a IRQ, based on [$0101]. IRQ: BIT $0101 $E1CA BMI $E1EA $E1CC BVC $E1D9 ;disk transfer routine ([$0101] = 01xxxxxx)

$E1CE $E1D1 $E1D4 $E1D5 $E1D6 $E1D7 $E1D8

LDX $4031 STA $4024 PLA PLA PLA TXA RTS

;disk byte skip routine ([$0101] = 00nnnnnn; n is # of bytes to skip) ;this is mainly used when the CPU has to do some calculations while bytes ;read off the disk need to be discarded. $E1D9 PHA $E1DA LDA $0101 $E1DD SEC $E1DE SBC #$01 $E1E0 BCC $E1E8 $E1E2 STA $0101 $E1E5 LDA $4031 $E1E8 PLA $E1E9 RTI ;[$0101] = 1Xxxxxxx $E1EA BVC $E1EF $E1EC JMP ($DFFE);

11xxxxxx

;disk IRQ acknowledge routine ([$0101] = 10xxxxxx). ;don't know what this is used for, or why a delay is put here. $E1EF PHA $E1F0 LDA $4030 $E1F3 JSR Delay131 $E1F6 PLA $E1F7 RTI ; ;load files ; ;loads files from disk into memory. ;params ;-----;RETaddr ;RETaddr+2

pointer to 10-byte disk header compare string pointer to list of files to identify & load

;the disk header compare string is compared against the first 10 bytes to ;come after the '*NINTENDO-HVC*' string in the first block. if any matches ;fail, an error is generated. If the compare string has a -1 in it, that ;skips the testing of that particular byte. Generally, this string is used to ;verify that the disk side and number data of a disk is corect. ;the file ID list is simply a list of files to be loaded from disk. These ;ID numbers (1 byte each) are tested against the file ID numbers of the ;individual files on disk, and matched file IDs results in that particular ;file being loaded into memory. The list is assumed to contain 20 ID's, but ;-1 can be placed at the end of the string to terminate the search ;prematurely. If -1 is the first ID in the string, this means that a system ;boot is to commence. Boot files are loaded via the BootID code in the first

;block of the disk. Files that match or are less than this BootID code are ;the ones that get loaded. Everytime a matching file is found, a counter is ;incremented. When the load finishes, this count will indicate how many files ;were found. No error checking occurs with the found file count. ;if an error occurs on the first try, the subroutine will make an additional ;attempt to read the disk, before returning with an error code other than 0. ;returns error # (if any) in A, and count of found files in Y. LoadFiles: $E1FA STA $E1FC LDA $E1FE JSR $E201 LDA $E204 PHA $E205 LDA $E207 STA $E209 JSR $E20C BEQ $E20E DEC $E210 BNE $E212 PLA $E213 STA $E216 LDY $E218 TXA $E219 RTS $E21A $E21D $E220 $E222 $E224 $E226 $E229 $E22C $E22F $E231 $E233 $E236 JSR JSR LDA BEQ LDA JSR JSR JSR DEC BNE JSR RTS LDA #$00 $0E #$ff; get 2 16-bit pointers GetHCPwNWPchk $0101 #$02; $05 $E21A $E212; $05; $E209 $0101 $0E error retry count return address if errors occur decrease retry count

ChkDiskHdr Get#ofFiles;returns # in [$06] $06 $E233; skip it all if none #$03 CheckBlkType FileMatchTest LoadData $06 $E224 XferDone

; ;Write file & set file count ; ;writes a single file to the last position on the disk, according to the ;disk's file count. uses header compare string, and pointer to file header ;structure (described in SaveData subroutine). ;this is the only mechanism the ROM BIOS provides for writing data to the ;disk, and it only lets you write one stinking file at a time! if that isn't ;enough, the disk's file count is modified everytime this routine is called ;so that the disk logically ends after the written file. ;logic:

;;;;;;-

if (WriteFile called) and (A <> -1), DiskFileCount := A disk is advanced to the end, in accordance to DiskFileCount writes data pointed to by RETaddr+2 to end of disk DiskFileCount is increased data is read back, and compared against data written if error occurs (like the comparison fails), DiskFileCount is decreased

;note that DiskFileCount is the actual recorded file count on the disk. ;load hardcoded parameters AppendFile: LDA #$ff; use current DiskFileCount WriteFile: STA $0E; specify file count in A $E23B LDA #$ff $E23D JSR GetHCPwWPchk;loads Y with [$0E] on error $E240 LDA $0101 $E243 PHA ;write $E244 $E246 $E248 $E24A $E24C $E24F ;verify $E251 $E253 $E255 $E258 $E25A $E25C data LDA STA DEC BEQ JSR BNE to end of disk #$03; 2 tries $05 $05 $E265 WriteLastFile $E248

data at end of disk LDA #$02 STA $05 JSR CheckLastFile BEQ $E265 DEC $05 BNE $E255

;if error occured during readback, hide last file $E25E STX $05; save error # $E260 JSR SetFileCnt $E263 LDX $05; restore error # ;return $E265 $E266 $E269 $E26A PLA STA $0101 TXA RTS

WriteLastFile: JSR ChkDiskHdr $E26E LDA $0E $E270 CMP #$ff $E272 BNE $E288 $E274 JSR Get#ofFiles $E277 JSR SkipFiles; advance to end of disk $E27A LDA #$03 $E27C JSR WriteBlkType $E27F LDA #$00 $E281 JSR SaveData; write out last file $E284 JSR XferDone $E287 RTS $E288 STA $06 $E28A JSR Set#ofFiles $E28D JMP $E277

CheckLastFile: JSR ChkDiskHdr $E293 LDX $06; load current file count $E295 INX $E296 TXA $E297 JSR Set#ofFiles;increase current file count $E29A JSR SkipFiles; skip to last file $E29D LDA #$03 $E29F JSR CheckBlkType $E2A2 LDA #$ff $E2A4 JSR SaveData; verify last file $E2A7 JSR XferDone $E2AA RTS ;sets file count via [$06] SetFileCnt: JSR ChkDiskHdr $E2AE LDA $06 $E2B0 JSR Set#ofFiles $E2B3 JSR XferDone $E2B6 RTS ; ;adjust file count ; ;reads disk's original file count, then subtracts the A value from it and ;writes the difference to the disk as the new file count. uses header compare ;string. if A is greater than original disk file count, error 31 is returned. ;this routine has 2 entry points. one which adjusts the current file count ;via A, and one which simply sets the file count to the A value. Since this ;routine makes a disk read cycle no matter which entry point is called, it is ;better to use SetFileCnt0/1 to simply set the disk file count to A. SetFileCnt2: LDX #$ff; $E2B9 BNE $E2BD AdjFileCnt: LDX #$00; $E2BD STX $09 $E2BF JSR GetHCPwWPchk $E2C2 LDA $0101 $E2C5 PHA ;get disk file count $E2C6 LDA #$03; 2 tries $E2C8 STA $05 $E2CA DEC $05 $E2CC BEQ $E2F1 $E2CE JSR GetFileCnt $E2D1 BNE $E2CA ;calculate difference $E2D3 LDA $06; $E2D5 SEC $E2D6 SBC $02; $E2D8 LDX $09 $E2DA BEQ $E2DE load file count calculate difference use A value use FileCnt-A

$E2DC $E2DE $E2E0 $E2E2

LDA LDX BCC STA

$02; #$31; $E2F1; $06

use original accumulator value branch if A is less than current file count

;set disk file count $E2E4 LDA #$02; 2 tries $E2E6 STA $05 $E2E8 JSR SetFileCnt $E2EB BEQ $E2F1 $E2ED DEC $05 $E2EF BNE $E2E8 $E2F1 $E2F2 $E2F5 $E2F6 PLA STA $0101 TXA RTS

;stores file count in [$06] GetFileCnt: JSR ChkDiskHdr $E2FA JSR Get#ofFiles $E2FD JSR XferDone $E300 RTS ; ;set disk file count ; ;this routine only rewrites a disk's file count (stored in block 2; specified ;in A). no other files are read/written after this. uses header compare ;string. SetFileCnt1: LDX #$01; add 1 to value in A $E303 BNE $E307 SetFileCnt0: LDX #$00; normal entry point $E307 STX $07 $E309 JSR GetHCPwWPchk $E30C LDA $0101 $E30F PHA $E310 CLC $E311 LDA $02; initial A value (or 3rd byte in HC parameter) $E313 ADC $07 $E315 STA $06 $E317 LDA #$02; 2 tries $E319 STA $05 $E31B JSR SetFileCnt $E31E BEQ $E324 $E320 DEC $05 $E322 BNE $E31B $E324 PLA $E325 STA $0101 $E328 TXA $E329 RTS ; ;get disk information

; ;this procedure reads the whole disk, and only returns information like ;disk size, filenames, etc. ;params ;-----;RETaddr ;info. format ;-----------;0 1 ;1 4 ;5 1 ;6 1 ;7 1 ;8 1 ;9 1 ;A 1

pointer to destination address for info. to collect

manufacturer code game name string game version disk side # disk #1 disk #2 disk #3 # of files on disk

; (the following block will appear for as many files as the files on disk ; byte indicates) ;B ;C 1 8 file ID code file name (ASCII)

; (the following is present after the last file info block) ;x ;x+1 1 1 disk size high byte disk size low byte

;returns error # (if any) in A. GetDiskInfo: LDA #$00 $E32C JSR GetHCPwNWPchk;get 1 16-bit pointer; put A in [$02] $E32F LDA $0101 $E332 PHA $E333 LDA #$02 $E335 STA $05 $E337 JSR $E346 $E33A BEQ $E340; escape if no errors $E33C DEC $05 $E33E BNE $E337 $E340 PLA $E341 STA $0101 $E344 TXA $E345 RTS ;start $E346 $E349 $E34B $E34D $E34F $E351 $E353 $E355 up disk read process JSR StartXfer; verify FDS string at beginning of disk LDA $00 STA $0A LDA $01 STA $0B LDY #$00 STY $02 STY $03

;load next 10 bytes off disk into RAM at Ptr($0A)

$E357 $E35A $E35C $E35D $E35F $E361

JSR STA INY CPY BNE JSR

XferByte ($0A),Y #$0a $E357 AddYtoPtr0A;add 10 to Word($0A)

;discard rest of data in this file (31 bytes) $E364 LDY #$1f $E366 JSR XferByte $E369 DEY $E36A BNE $E366 ;get # $E36C $E36F $E372 $E374 $E376 $E378 of files JSR EndOfBlkRead JSR Get#ofFiles;stores it in [$06] LDY #$00 LDA $06 STA ($0A),Y; store # of files in ([$0A]) BEQ $E3CB; branch if # of files = 0

;get info for next file $E37A LDA #$03 $E37C JSR CheckBlkType $E37F JSR XferByte; discard file sequence # $E382 JSR XferByte; file ID code $E385 LDY #$01 $E387 STA ($0A),Y; store file ID code ;store $E389 $E38A $E38D $E38F $E391 $E393 $E396 $E399 file INY JSR STA CPY BNE name string (8 letters) XferByte ($0A),Y #$09 $E389

JSR AddYtoPtr0A;advance 16-bit dest ptr JSR XferByte; throw away low load address JSR XferByte; throw away high load address

;Word($02) += $105 + FileSize $E39C CLC $E39D LDA #$05 $E39F ADC $02 $E3A1 STA $02 $E3A3 LDA #$01 $E3A5 ADC $03 $E3A7 STA $03 $E3A9 JSR XferByte; get low FileSize $E3AC STA $0C $E3AE JSR XferByte; get high FileSize $E3B1 STA $0D $E3B3 CLC $E3B4 LDA $0C $E3B6 ADC $02 $E3B8 STA $02 $E3BA LDA $0D $E3BC ADC $03 $E3BE STA $03 $E3C0 LDA #$ff

$E3C2 $E3C4 $E3C7 $E3C9 ;store $E3CB $E3CD $E3CF $E3D1 $E3D3 $E3D4 $E3D6 $E3D9

STA JSR DEC BNE

$09 RdData; $06; $E37A

dummy read data off disk decrease file count #

out disk size LDA $03 LDY #$01; STA ($0A),Y LDA $02 INY STA ($0A),Y JSR XferDone RTS

fix-up from RdData

;adds Y to Word(0A) AddYtoPtr0A: TYA $E3DB CLC $E3DC ADC $0A $E3DE STA $0A $E3E0 LDA #$00 $E3E2 ADC $0B $E3E4 STA $0B $E3E6 RTS ; ;get hard-coded pointer(s) ; ;this routine does 3 things. First, it fetches 1 or 2 hardcoded 16-bit ;pointers that follow the second return address. second, it checks the ;disk set or even write-protect status of the disk, and if the checks fail, ;the first return address on the stack is discarded, and program control is ;returned to the second return address. finally, it saves the position of ;the stack so that when an error occurs, program control will be returned to ;the same place. ;params ;-----;2nd call addr 1 or 2 16-bit pointers ;A ; -1 2 16-bit pointers are present other values 1 16-bit pointer present

;rtns (no error) ;--------------;PC original call address ;A 00

;[$00] where parameters were loaded (A is placed in [$02] if not -1) ;(error) ;------;PC second call address ;Y byte stored in [$0E]

;A ;

01 03

if disk wasn't set if disk is write-protected

;entry points GetHCPwNWPchk: SEC; $E3E8 BCS $E3EB GetHCPwWPchk: CLC;

don't do write-protect check check for write protection

;load 2nd return address into Ptr($05) $E3EB TSX $E3EC DEX $E3ED STX $04; store stack pointer-1 in [$04] $E3EF PHP $E3F0 STA $02 $E3F2 LDY $0104,X $E3F5 STY $05 $E3F7 LDY $0105,X $E3FA STY $06 ;load 1st 16-bit parameter into Ptr($00) $E3FC TAX $E3FD LDY #$01 $E3FF LDA ($05),Y $E401 STA $00 $E403 INY $E404 LDA ($05),Y $E406 STA $01 $E408 LDA #$02 ;load 2nd 16-bit parameter into Ptr($02) if A was originally -1 $E40A CPX #$ff $E40C BNE $E41A $E40E INY $E40F LDA ($05),Y $E411 STA $02 $E413 INY $E414 LDA ($05),Y $E416 STA $03 $E418 LDA #$04 ;increment 2nd return address appropriately $E41A LDX $04 $E41C CLC $E41D ADC $05 $E41F STA $0104,X $E422 LDA #$00 $E424 ADC $06 $E426 STA $0105,X ;test disk set status flag $E429 PLP $E42A LDX #$01; disk set error $E42C LDA $4032 $E42F AND #$01 $E431 BNE $E43E $E433 BCS $E444; skip write-protect check ;test write-protect status

$E435 $E437 $E43A $E43C

LDX LDA AND BEQ

#$03; $4032 #$04 $E444

write-protect error

;discard return address if tests fail $E43E PLA $E43F PLA $E440 LDY $0E $E442 TXA $E443 CLI $E444 RTS ; ;disk header check ; ;routine simply compares the first 10 bytes on the disk coming after the FDS ;string, to 10 bytes pointed to by Ptr($00). To bypass the checking of any ;byte, a -1 can be placed in the equivelant place in the compare string. ;Otherwise, if the comparison fails, an appropriate error will be generated. ChkDiskHdr: $E448 LDX $E44A STX $E44C LDY $E44E JSR $E451 CMP $E453 BEQ $E455 LDX $E457 CPX $E459 BNE $E45B LDX $E45D LDA $E45F CMP $E461 JSR $E464 INY $E465 CPY $E467 BEQ $E469 CPY $E46B BCC $E46D INC $E46F CPY $E471 BNE $E473 JSR $E476 STA $E478 LDY $E47A JSR $E47D DEY $E47E BNE $E480 JSR $E483 RTS JSR StartXfer; check FDS string #$04 $08 #$00 XferByte ($00),Y; compares code to byte stored at [Ptr($00)+Y] $E464 $08 #$0a $E45D #$10 ($00),Y #$ff XferFailOnNEQ #$01 $E46D #$05 $E46F $08 #$0a $E44E XferByte; $08 #$1e; XferByte; $E47A EndOfBlkRead

boot read file code 30 iterations dummy read 'til end of block

; ;file count block routines ; ;these routines specifically handle reading & writing of the file count

block ;stored on FDS disks. ;loads # of files recorded in block type #2 into [$06] Get#ofFiles: LDA #$02 $E486 JSR CheckBlkType $E489 JSR XferByte $E48C STA $06 $E48E JSR EndOfBlkRead $E491 RTS ;writes # of files (via A) to be recorded on disk. Set#ofFiles: PHA $E493 LDA #$02 $E495 JSR WriteBlkType $E498 PLA $E499 JSR XferByte; write out disk file count $E49C JSR EndOfBlkWrite $E49F RTS ; ;file match test ; ;this routine uses a byte string pointed at by Ptr($02) to tell the disk ;system which files to load. The file ID's number is searched for in the ;string. if an exact match is found, [$09] is 0'd, and [$0E] is incremented. ;if no matches are found after 20 bytes, or a -1 entry is encountered, [$09] ;is set to -1. if the first byte in the string is -1, the BootID number is ;used for matching files (any FileID that is not greater than the BootID ;qualifies as a match). ;logic: ;if String[0] = -1 then ; if FileID <= BootID then ; [$09]:=$00 ; Inc([$0E]) ;else ; I:=0 ; while (String[I]<>FileID) or (String[I]<>-1) or (I<20) do Inc(I) ; if String[I] = FileID then ; [$09]:=$00 ; Inc([$0E]) ; else ; [$09]:=$FF; FileMatchTest: JSR XferByte; file sequence # $E4A3 JSR XferByte; file ID # (gets loaded into X) $E4A6 LDA #$08; set IRQ mode to skip next 8 bytes $E4A8 STA $0101 $E4AB CLI $E4AC LDY #$00 $E4AE LDA ($02),Y $E4B0 CMP #$ff; if Ptr($02) = -1 then test boot ID code $E4B2 BEQ $E4C8

$E4B4 $E4B5 $E4B7 $E4B9 $E4BA $E4BC $E4BE $E4C0 $E4C2 $E4C4 $E4C6 $E4C8 $E4CA $E4CC $E4CE $E4D0 $E4D2 $E4D4 $E4D7 $E4D9

TXA; file ID # CMP ($02),Y BEQ $E4CE INY CPY #$14 BEQ $E4C4 LDA ($02),Y CMP #$ff BNE $E4B4 LDA BNE CPX BEQ BCS LDA INC STA LDA BNE RTS #$ff $E4D2 $08; $E4CE $E4D2; #$00 $0E $09 $0101 $E4D4;

compare boot read file code to current branch if above (or equal, but isn't possible)

wait until all 8 bytes have been read

; ;skip files ; ;this routine uses the value stored in [$06] to determine how many files to ;dummy-read (skip over) from the current file position. SkipFiles: $E4DC STA $E4DE BEQ $E4E0 LDA $E4E2 JSR $E4E5 LDY $E4E7 JSR $E4EA DEY $E4EB BNE $E4ED LDA $E4EF STA $E4F1 JSR $E4F4 DEC $E4F6 BNE $E4F8 RTS LDA $06 $08 $E4F8; branch if file count = 0 #$03 CheckBlkType #$0a; skip 10 bytes XferByte $E4E7 #$ff $09 LoadData; $08 $E4E0

dummy read file data

; ;load file off disk into memory ; ;loads data from current file off disk into a destination address specified ;by the file's header information stored on disk. ;params ;-----;[$09]: dummy read only (if not zero)

LoadData: $E4FB JSR $E4FE STA $E501 INY $E502 CPY $E504 BNE ;Ptr($0A): ;Ptr($0C): RdData: $E509 $E50C $E50D $E510 $E512 $E515 $E517 $E518 $E51A $E51B $E51D $E51F $E521 $E523 JSR JSR PHA JSR LDA JSR LDY PLA BNE CLC LDA ADC LDA ADC BCS

LDY #$00 Xfer1stByte $000A,Y #$04 $E4FB destination address byte xfer count DecPtr0C XferByte; EndOfBlkRead #$04 CheckBlkType $09 $E549; $0A $0C $0B $0D $E531; copy to VRAM if not zero get kind of file

branch if (DestAddr+XferCnt)<10000h

;if DestAddr < 0200h then do dummy copying $E525 LDA $0B $E527 CMP #$20 $E529 BCS $E533; branch if DestAddr >= 2000h $E52B AND #$07 $E52D CMP #$02 $E52F BCS $E533; branch if DestAddr >= 0200h $E531 LDY #$ff $E533 $E536 $E538 $E53A $E53C $E53E $E540 $E542 $E545 $E547 JSR CPY BNE STA INC BNE INC JSR BCS BCC XferByte #$00 $E542 ($0A),Y $0A $E542 $0B DecPtr0C $E533 $E572

;VRAM data copy $E549 CPY #$00 $E54B BNE $E563 $E54D LDA $FE $E54F AND #$e7 $E551 STA $FE $E553 STA $2001; $E556 LDA $2002; $E559 LDA $0B $E55B STA $2006; $E55E LDA $0A $E560 STA $2006;

[NES] PPU setup #2 [NES] PPU status [NES] VRAM address select [NES] VRAM address select

$E563 $E566 $E568 $E56A $E56D $E570 $E572 $E574 $E576 $E579 $E57A $E57D $E580

JSR CPY BNE STA JSR BCS LDA BNE JSR RTS JSR JSR JMP

XferByte #$00 $E56D $2007; DecPtr0C $E563 $09 $E57A EndOfBlkRead XferByte XferByte ChkDiskSet

[NES] VRAM data

; ;load size & source address operands into $0A..$0D ; ;this routine is used only for when writing/verifying file data on disk. it ;uses the data string at Ptr($02) to load size and source address operands ;into Ptr($0C) and Ptr($0A), respectfully. It also checks if the source ;address is from video memory, and programs the PPU address register if so. ;load size of file via string offset $0B into Word($0C) LoadSiz&Src: LDY #$0b $E585 LDA ($02),Y; file size LO $E587 STA $0C $E589 INY $E58A LDA ($02),Y; file size HI $E58C STA $0D ;load source address via string offset $0E into Ptr($0A) $E58E LDY #$0e $E590 LDA ($02),Y; source address LO $E592 STA $0A $E594 INY $E595 LDA ($02),Y; source address HI $E597 STA $0B ;load source type byte (anything other than 0 means use PPU memory) $E599 INY $E59A LDA ($02),Y $E59C BEQ $E5B1 ;program PPU address registers with source address $E59E JSR DisPfOBJ $E5A1 LDA $2002; reset flip-flop $E5A4 LDA $0B $E5A6 STA $2006; store HI address $E5A9 LDA $0A $E5AB STA $2006; store LO address $E5AE LDA $2007; discard first read $E5B1 JSR DecPtr0C; adjust transfer count for range (0..n-1) $E5B4 RTS ; ;save data in memory to file

on disk ; ;this routine does 2 things, which involve working with file data. if called ;with A set to 0, file data is written to disk from memory. if called with ;A <> 0, file data on disk is verified (compared to data in memory). Ptr($02) ;contains the address to a 17-byte structure described below. Note that the ;disk transfer direction bit ($4025.2) must be set in sync with A, since this ;routine will not modify it automatically. ;00 ;01 ;09 ;0B ;0D ;0E ;10 ;11 1 8 2 2 1 2 1 ID Name load address Size type (0 = CPU data) source address of file data (NOT written to disk) source address type (0 = CPU; NOT written to disk)

;the first 14 bytes of the structure are used directly as the file header ;data. the file sequence # part of the file header is specified seperately ;(in [$06]). Data at offset 0E and on is not used as file header data. ;offset 0E of the structure specifies the address in memory which the actual ;file data resides. offset 10 specifies the source memory type (0 = CPU; ;other = PPU). ;entry point SaveData: STA $09; value of A is stored in [$09] $E5B7 LDA $06; load current file # $E5B9 JSR XferByte; write out file sequence # (from [$06]) $E5BC LDX $09 $E5BE BEQ $E5C7; [$09] should be set to jump when writing $E5C0 LDX #$26; error # $E5C2 CMP $06; cmp. recorded sequence # to what it should be $E5C4 JSR XferFailOnNEQ ;loop to write/check entire file header block (minus $E5C7 LDY #$00 $E5C9 LDA ($02),Y; load header byte $E5CB JSR XferByte; write it out (or read it in) $E5CE LDX $09 $E5D0 BEQ $E5D9; jump around check if writing $E5D2 LDX #$26; error # $E5D4 CMP ($02),Y; cmp. recorded header byte to $E5D6 JSR XferFailOnNEQ $E5D9 INY; advance pointer position $E5DA CPY #$0e; loop is finished if 14 bytes $E5DC BNE $E5C9 ;set up $E5DE $E5E0 $E5E2 $E5E5 $E5E8 $E5EA next block for reading LDX $09 BEQ $E616; branch if writing instead JSR EndOfBlkRead JSR LoadSiz&Src;sets up Ptr($0A) & Ptr($0C) LDA #$04 JSR CheckBlkType the file sequence #)

data to disk what it should be have been checked

;check $E5ED $E5EF $E5F1 $E5F3 $E5F5 $E5F7 ;check $E5F9 $E5FC $E5FE $E600 $E603 $E606 $E608 ;write $E60A $E60C $E60F $E612 $E614 ;set up $E616 $E619 $E61C $E61E $E621 ;verify $E624 $E626 $E628 $E62B $E62D $E630 $E633 $E636

source type and LDY #$10 LDA ($02),Y; BNE $E624; LDY #$00 LDX $09; BEQ $E60A; data JSR LDX CMP JSR JSR BCS BCC data LDA JSR JSR BCS BCC

read/verify status check data source type bit branch if NOT in CPU memory map (PPU instead) check if reading or writing branch if writing

on disk XferByte #$26 ($0A),Y XferFailOnNEQ inc0Adec0C $E5F9 $E638 to disk ($0A),Y XferByte inc0Adec0C $E60A $E638

next block for writing JSR EndOfBlkWrite JSR LoadSiz&Src;sets up Ptr($0A) & Ptr($0C) LDA #$04 JSR WriteBlkType JMP $E5ED data on disk with VRAM LDX $09 BEQ $E640 JSR XferByte LDX #$26; error # CMP $2007 JSR XferFailOnNEQ JSR DecPtr0C BCS $E624

;end block reading $E638 LDX $09 $E63A BEQ $E649; branch if writing instead $E63C JSR EndOfBlkRead $E63F RTS ;write $E640 $E643 $E646 data LDA JSR JMP from VRAM to disk $2007; [NES] VRAM data XferByte $E633

;end block writing $E649 JSR EndOfBlkWrite $E64C RTS ; ; ;

;waits until drive is ready (i.e., the disk head is at the start of the disk) WaitForRdy: JSR StopMotor $E650 LDY #$00 $E652 JSR MilSecTimer;0.256 sec delay $E655 JSR MilSecTimer;0.256 sec delay $E658 JSR StartMotor $E65B LDY #$96 $E65D JSR MilSecTimer;0.150 sec delay $E660 LDA $F9 $E662 ORA #$80; enable battery checking $E664 STA $F9 $E666 STA $4026 $E669 LDX #$02; battery error $E66B EOR $4033 $E66E ROL A $E66F JSR XferFailOnCy $E672 JSR StopMotor $E675 JSR StartMotor $E678 LDX #$01; disk set error $E67A LDA $4032 $E67D LSR A; check disk set bit $E67E JSR XferFailOnCy $E681 LSR A; check ready bit $E682 BCS $E678; wait for drive to become ready $E684 RTS ;stop disk drive motor StopMotor: LDA $FA $E687 AND #$08 $E689 ORA #$26 $E68B STA $4025 $E68E RTS ;verifies that first byte in file is equal to value in accumulator CheckBlkType: LDY #$05 $E691 JSR MilSecTimer;0.005 sec delay $E694 STA $07 $E696 CLC $E697 ADC #$21; error # = 21h + failed block type (1..4) $E699 TAY $E69A LDA $FA $E69C ORA #$40 $E69E STA $FA $E6A0 STA $4025 $E6A3 JSR Xfer1stByte $E6A6 PHA $E6A7 TYA $E6A8 TAX $E6A9 PLA $E6AA CMP $07 $E6AC JSR XferFailOnNEQ $E6AF RTS ;writes out block start mark, plus byte in accumulator WriteBlkType: LDY #$0a $E6B2 STA $07 $E6B4 LDA $FA $E6B6 AND #$2b; set xfer direction to write $E6B8 STA $4025

$E6BB $E6BE $E6C0 $E6C3 $E6C5 $E6C7 $E6CA $E6CC $E6CF $E6D1 $E6D4

JSR LDY STY ORA STA STA LDA JSR LDA JSR RTS

MilSecTimer;0.010 sec delay #$00 $4024; zero out write register #$40; tell FDS to write data to disk NOW $FA $4025 #$80 Xfer1stByte;write out block start mark $07 XferByte; write out block type

;FDS string FDSstr DB '*CVH-ODNETNIN*' ;starts transfer StartXfer: JSR WaitForRdy $E6E6 LDY #$c5 $E6E8 JSR MilSecTimer;0.197 sec delay $E6EB LDY #$46 $E6ED JSR MilSecTimer;0.070 sec delay $E6F0 LDA #$01 $E6F2 JSR CheckBlkType $E6F5 LDY #$0d $E6F7 JSR XferByte $E6FA LDX #$21; error 21h if FDS string failed comparison $E6FC CMP FDSstr,Y $E6FF JSR XferFailOnNEQ $E702 DEY $E703 BPL $E6F7 $E705 RTS ;checks the CRC OK bit at the end of a block EndOfBlkRead: JSR XferByte; first CRC byte $E709 LDX #$28; premature file end error # $E70B LDA $4030 $E70E AND #$40; check "end of disk" status $E710 BNE XferFail $E712 LDA $FA $E714 ORA #$10; set while processing block end mark (CRC) $E716 STA $FA $E718 STA $4025 $E71B JSR XferByte; second CRC byte $E71E LDX #$27; CRC fail error # $E720 LDA $4030 $E723 AND #$10; test CRC bit $E725 BNE XferFail $E727 BEQ ChkDiskSet ;takes care of writing CRC value out to block being written EndOfBlkWrite: JSR XferByte $E72C LDX #$29 $E72E LDA $4030 $E731 AND #$40 $E733 BNE XferFail $E735 LDA $FA $E737 ORA #$10; causes FDS to write out CRC immediately $E739 STA $FA; following completion of pending byte write $E73B STA $4025 $E73E LDX #$b2; 0.0005 second delay (to allow adaptor pleanty

$E740 $E741 $E743 $E745 $E748 $E74A

DEX; of time to write out entire CRC) BNE $E740 LDX #$30 LDA $4032 AND #$02 BNE XferFail

;disables disk transfer interrupts & checks disk set status ChkDiskSet: LDA $FA $E74E AND #$2f $E750 ORA #$04 $E752 STA $FA $E754 STA $4025 $E757 LDX #$01; disk set error # $E759 LDA $4032 $E75C LSR A $E75D JSR XferFailOnCy $E760 RTS ;reads in CRC value at end of block into Ptr($0A)+Y. Note that this ;subroutine is not used by any other disk routines. ReadCRC: JSR XferByte $E764 STA ($0A),Y $E766 LDX #$28 $E768 LDA $4030 $E76B AND #$40 $E76D BNE XferFail $E76F INY $E770 JSR XferByte $E773 STA ($0A),Y $E775 JMP ChkDiskSet ;dispatched when transfer is to be terminated. returns error # in A. XferDone: LDX #$00; no error $E77A BEQ $E786 XferFailOnCy: BCS XferFail $E77E RTS XferFailOnNEQ: BEQ $E77E XferFail: TXA $E782 LDX $04 $E784 TXS; restore PC to original caller's address $E785 TAX $E786 LDA $FA $E788 AND #$09 $E78A ORA #$26 $E78C STA $FA $E78E STA $4025 $E791 TXA $E792 CLI $E793 RTS ;the main interface for data exchanges between the disk drive & the system. Xfer1stByte: LDX #$40 $E796 STX $0101 $E799 ROL $FA $E79B SEC $E79C ROR $FA $E79E LDX $FA $E7A0 STX $4025 XferByte: CLI

$E7A4

JMP $E7A4

;routine for incrementing 16-bit pointers in the zero-page inc0Adec0C: INC $0A $E7A9 BNE DecPtr0C $E7AB INC $0B DecPtr0C: SEC $E7AE LDA $0C $E7B0 SBC #$01 $E7B2 STA $0C $E7B4 LDA $0D $E7B6 SBC #$00 $E7B8 STA $0D $E7BA RTS ; ;PPU data processor ; ;this routine treats a string of bytes as custom instructions. These strings ;are stored in a mannar similar to regular CPU instructions, in that the ;instructions are stored & processed sequentially, and instruction length is ;dynamic. The instructions haved been designed to allow easy filling/copying ;of data to random places in the PPU memory map. Full random access to PPU ;memory is supported, and it is even possible to call subroutines. All data ;written to PPU memory is stored in the actual instructions (up to 64 ;sequential bytes of data can be stored in 1 instruction). ;the 16-bit immediate which follows the 'JSR PPUdataPrsr' opcode is a pointer ;to the first PPU data string to be processed. ;the PPU data processor's opcodes are layed out as follows. ;+---------------+ ;|special opcodes| ;+---------------+ ; $4C: call subroutine. 16-bit call address follows. This opcode is ; the equivelant of a 'JMP $xxxx' 6502 mnemonic. ; $60: end subroutine. returns to the instruction after the call. ; This opcode is the equivelant of a 'RTS' 6502 mnemonic. ; $80..$FF: end program. processing will not stop until this opcode is ; encountered. ;+---------------------------------+ ;|move/fill data instruction format| ;+---------------------------------+ ; ; ; ; byte 0 -----high byte of destination PPU address. cannot be equal to $60, $4C or greater than $7F.

; byte 1 ; ------

; low byte of destination PPU address. ; ; ; ; ; ; ; ; ; ; ; ; byte 2 bit description ---------------------7: increment PPU address by 1/32 (0/1) 6: copy/fill data to PPU mem (0/1) 5-0: byte xfer count (0 indicates 64 bytes) bytes 3..n ---------- if the fill bit is set, there is only one byte here, which is the value to fill the PPU memory with, for the specified byte xfer count. - if copy mode is set instead, this data contains the bytes to be copied to PPU memory. The number of bytes appearing here is equal to the byte xfer count.

;entry point PPUdataPrsr: JSR GetHCparam $E7BE JMP $E815 ;[Param] is in A $E7C1 PHA; save [Param] $E7C2 STA $2006; [NES] VRAM address select $E7C5 INY $E7C6 LDA ($00),Y; load [Param+1] $E7C8 STA $2006; [NES] VRAM address select $E7CB INY $E7CC LDA ($00),Y; load [Param+2] IFcccccc $E7CE ASL A; bit 7 in carry Fcccccc0 $E7CF PHA; save [Param+2] ;if Bit(7,[Param+2]) then PPUinc:=32 else PPUinc:=1 $E7D0 LDA $FF $E7D2 ORA #$04 $E7D4 BCS $E7D8 $E7D6 AND #$fb $E7D8 STA $2000; [NES] PPU setup #1 $E7DB STA $FF ;if Bit(6,[Param+2]) then $E7DD PLA; load [Param+2] Fcccccc0 $E7DE ASL A $E7DF PHP; save zero status $E7E0 BCC $E7E5 $E7E2 ORA #$02 $E7E4 INY; advance to next byte if fill bit set ;if Zero([Param+2] and $3F) then carry:=1 else carry:=0 $E7E5 PLP $E7E6 CLC $E7E7 BNE $E7EA $E7E9 SEC $E7EA ROR A $E7EB LSR A $E7EC TAX ;for I:=0 to X-1 do [$2007]:=[Param+3+(X and not Bit(6,[Param+2]))] $E7ED BCS $E7F0 $E7EF INY

$E7F0 $E7F2 $E7F5 $E7F6

LDA ($00),Y STA $2007; DEX BNE $E7ED

[NES] VRAM data

;not sure what this is supposed to do, since it looks like it's zeroing out ;the entire PPU address register in the end $E7F8 PLA; load [Param] $E7F9 CMP #$3f $E7FB BNE $E809 $E7FD STA $2006; [NES] VRAM address select $E800 STX $2006; [NES] VRAM address select $E803 STX $2006; [NES] VRAM address select $E806 STX $2006; [NES] VRAM address select ;increment Param by Y+1 $E809 SEC $E80A TYA $E80B ADC $00 $E80D STA $00 $E80F LDA #$00 $E811 ADC $01 $E813 STA $01 ;exit if bit(7,[Param]) is 1 $E815 LDX $2002; [NES] PPU status $E818 LDY #$00 $E81A LDA ($00),Y; load opcode $E81C BPL $E81F $E81E RTS ;test for RET instruction $E81F CMP #$60 $E821 BNE $E82D ;[Param] = $60: ;pop Param off stack $E823 PLA $E824 STA $01 $E826 PLA $E827 STA $00 $E829 LDY #$02; $E82B BNE $E809; ;test for JSR opcode $E82D CMP #$4c $E82F BNE $E7C1 ;[Param] = $4C ;push Param onto stack $E831 LDA $00 $E833 PHA $E834 LDA $01 $E836 PHA ;Param $E837 $E838 $E83A $E83B = [Param+1] INY LDA ($00),Y TAX INY

increment amount unconditional

$E83C $E83E $E840 $E842

LDA STA STX BCS

($00),Y $01 $00 $E815;

unconditional

; ; ; ;fetches hardcoded 16-bit value after second return address into [$00] & [$01] ;that return address is then incremented by 2. GetHCparam: TSX $E845 LDA $0103,X $E848 STA $05 $E84A LDA $0104,X $E84D STA $06 $E84F LDY #$01 $E851 LDA ($05),Y $E853 STA $00 $E855 INY $E856 LDA ($05),Y $E858 STA $01 $E85A CLC $E85B LDA #$02 $E85D ADC $05 $E85F STA $0103,X $E862 LDA #$00 $E864 ADC $06 $E866 STA $0104,X $E869 RTS $E86A $E86C $E86E $E871 $E873 $E876 $E878 $E87A $E87B $E87E $E87F $E882 $E885 $E886 $E889 $E88A $E88D $E890 $E891 $E893 $E894 $E896 $E898 $E89B $E89E $E8A1 $E8A4 LDA AND STA STA LDX LDY BEQ PHA STA INY LDA STA INY LDX INY LDA STA DEX BNE PLA CMP BNE STA STX STX STX INY $FF #$fb $2000; $FF $2002; #$00 $E8A5 $2006; $0302,Y $2006; $0302,Y $0302,Y $2007; $E889 #$3f $E8A4 $2006; $2006; $2006; $2006; [NES] VRAM data

[NES] PPU setup #1 [NES] PPU status

[NES] VRAM address select [NES] VRAM address select

[NES] [NES] [NES] [NES]

VRAM VRAM VRAM VRAM

address address address address

select select select select

$E8A5 $E8A8 $E8AA $E8AD $E8AF $E8B2 $E8B3 $E8B6 $E8B9 $E8BC $E8BD $E8C0 $E8C3 $E8C4 $E8C7 $E8CA $E8CD $E8CE $E8CF $E8D1 $E8D2 $E8D4 $E8D6 $E8D8 $E8DB $E8DD $E8DF $E8E1 $E8E3 $E8E5 $E8E8 $E8EA $E8EC $E8EE $E8F0 $E8F2 $E8F3 $E8F4 $E8F5 $E8F6 $E8F8 $E8FB $E8FD $E900 $E903 $E905 $E908 $E90B $E90D $E90F $E912 $E915 $E916 $E918 $E91B $E91D

LDA BPL STA LDA STA RTS LDA LDA STA INX LDA STA INX LDA LDA STA INX DEY BNE RTS STA STX STY JSR LDY LDA BNE STA STX JSR LDY LDA AND STA LDA LSR LSR LSR LSR STA LDX LDA STA JSR LDA STA JSR LDA STA STA JSR INY LDA STA DEC BNE

$0302,Y $E87A $0302 #$00 $0301

$2002; $0300,X $2006; $0300,X $2006; $2007; $2007; $0300,X $E8B6

[NES] PPU status [NES] VRAM address select [NES] VRAM address select [NES] VRAM data [NES] VRAM data

$03 $02 $04 GetHCparam #$ff #$01 $E8F6 $03 $02 GetHCparam #$00 ($00),Y #$0f $04 ($00),Y A A A A $05 $0301 $03 $0302,X $E93C $02 $0302,X $E93C $04 $06 $0302,X $E93C ($00),Y $0302,X $06 $E912

$E91F $E922 $E925 $E926 $E928 $E92A $E92C $E92E $E930 $E932 $E934 $E936 $E938 $E93B $E93C $E93D $E940 $E942 $E945 $E947 $E94A $E94B $E94C $E94E $E94F $E950 $E951 $E952 $E953 $E954 $E956 $E957 $E959 $E95A $E95B $E95E $E960 $E962 $E963 $E966 $E968 $E96A $E96B $E96E $E96F $E970 $E972 $E975 $E976 $E978 $E97B $E97C

JSR STX CLC LDA ADC STA LDA ADC STA DEC BNE LDA STA RTS INX CPX BCC LDX LDA STA PLA PLA LDA RTS DEX DEX DEX TXA CLC ADC DEY BNE TAX TAY LDA CMP BNE INX LDA CMP BNE INX LDA CLC RTS LDA STA INY LDA STA SEC RTS

$E93C $0301 #$20 $02 $02 #$00 $03 $03 $05 $E8FB #$ff $0302,X

$0300 $E94E $0301 #$ff $0302,X #$01

#$03 $E953 $0300,X $00 $E970 $0300,X $01 $E970 $0300,X

$00 $0300,Y $01 $0300,Y

$E97D $E97F $E981 $E983 $E984 $E986 $E987 $E989 $E98B $E98D $E98F $E990 $E991 $E992 $E994 $E996 $E997 $E999 $E99A $E99B $E99C $E99E $E9A0 $E9A2 $E9A4 $E9A5 $E9A7 $E9A8 $E9AA $E9AC $E9AE $E9B0

LDA STA LDA ASL ROL ASL ROL AND STA LDA LSR LSR LSR ORA STA RTS LDA ASL ASL ASL STA LDA STA LDA LSR ROR LSR ROR LDA AND STA RTS

#$08 $00 $02 A $00 A $00 #$e0 $01 $03 A A A $01 $01

$01 A A A $03 $01 $02 $00 A $02 A $02 #$f8 $02 $02

; ;Random number generator ; ;uses a shift register and a XOR to generate pseudo-random numbers. ;algorithm ;--------;carry [X] [X+1] ;--> ---> ---> ;C 765432*0

[X+n] ---> ---> 765432*0

---> ---> 76543210

...

;notes ;----;* these 2 points are XORed, and the result is stored in C. ;- when the shift occurs, C is shifted into the MSB of [X], the LSB of [X] is ; shifted into the MSB of [X+1], and so on, for as many more numbers there ; are (# of bytes to use is indicated by Y). ;- at least 2 8-bit shift registers need to be used here, but using more will ; not effect the random number generation. Also, after 16 shifts, the

; 16-bit results in the first 2 bytes will be the same as the next 2, so ; it's really not neccessary to use more than 2 bytes for this algorithm. ;- a new random number is available after each successive call to this ; subroutine, but to get a good random number to start off with, it may be ; neccessary to call this routine several times. ;- upon the first time calling this routine, make sure the first 2 bytes ; do not both contain 0, otherwise the random number algorithm won't work. ;Y is number of 8-bit registers to use (usually 2) ;X is base 0pg addr for shifting ;store first bit sample RndmNbrGen: LDA $00,X $E9B3 AND #$02 $E9B5 STA $00 ;xor second $E9B7 LDA $E9B9 AND $E9BB EOR bit sample with first $01,X #$02 $00

;set carry to result of XOR $E9BD CLC $E9BE BEQ $E9C1 $E9C0 SEC ;multi-precision shift for Y amount of bytes $E9C1 ROR $00,X $E9C3 INX $E9C4 DEY $E9C5 BNE $E9C1 $E9C7 RTS ; ; ; $E9C8 $E9CA $E9CD $E9CF $E9D2 $E9D3 $E9D5 $E9D7 $E9D9 $E9DB $E9DD $E9DE $E9DF $E9E1 $E9E3 $E9E5 LDA STA LDA STA RTS STX DEC BPL LDA STA TYA TAX LDA BEQ DEC DEX #$00 $2003; #$02 $4014; [NES] SPR-RAM address select [NES] Sprite DMA trigger

$00 $00,X $E9DE #$09 $00,X $00,X $E9E5 $00,X

$E9E6 $E9E8 $E9EA

CPX $00 BNE $E9DF RTS

; ;controller read function ;;;;;strobes controllers [$F5] contains 8 reads [$00] contains 8 reads [$F6] contains 8 reads [$01] contains 8 reads LDX $FB $4016; $4016; #$08 $4016; A $F5 A $00 $4017; A $F6 A $01 $E9F7 [NES] Joypad & I/O port for port #1 [NES] Joypad & I/O port for port #1 [NES] Joypad & I/O port for port #1 of of of of bit bit bit bit 0 1 0 1 from from from from [$4016] [$4016] [$4017] [$4017]

ReadCtrlrs: $E9ED INX $E9EE STX $E9F1 DEX $E9F2 STX $E9F5 LDX $E9F7 LDA $E9FA LSR $E9FB ROL $E9FD LSR $E9FE ROL $EA00 LDA $EA03 LSR $EA04 ROL $EA06 LSR $EA07 ROL $EA09 DEX $EA0A BNE $EA0C RTS

[NES] Joypad & I/O port for port #2

; ;controller OR function ;[$F5]|=[$00] ;[$F6]|=[$01] ORctrlrRead: LDA $00 $EA0F ORA $F5 $EA11 STA $F5 $EA13 LDA $01 $EA15 ORA $F6 $EA17 STA $F6 $EA19 RTS ; ;get controller status ;- returns status of controller buttons in [$F7] (CI) and [$F8] (CII) ;- returns which new buttons have been pressed since last update in ; [$F5] (CI) and [$F6] (CII) GetCtrlrSts: JSR ReadCtrlrs $EA1D BEQ $EA25; always branches because ReadCtrlrs sets zero flag $EA1F JSR ReadCtrlrs; this instruction is not used

$EA22 $EA25 $EA27 $EA29 $EA2A $EA2C $EA2E $EA30 $EA32 $EA33 $EA35

JSR LDX LDA TAY EOR AND STA STY DEX BPL RTS

ORctrlrRead;this instruction is not used #$01 $F5,X $F7,X $F5,X $F5,X $F7,X $EA27

; $EA36 $EA39 $EA3B $EA3D $EA3E $EA41 $EA42 $EA44 $EA46 $EA48 $EA4A $EA4C $EA4F $EA52 $EA54 $EA56 $EA57 $EA5A $EA5D $EA5E $EA60 $EA62 $EA64 $EA66 $EA68 $EA6B $EA6D $EA6F $EA71 $EA73 $EA75 $EA77 $EA78 $EA7A $EA7C $EA7E $EA80 $EA81 $EA83 JSR LDY LDA PHA JSR PLA CMP BNE CPY BNE BEQ JSR JSR LDY LDA PHA JSR JSR PLA CMP BNE CPY BNE BEQ JSR LDA STA LDA STA LDX LDA TAY EOR AND STA STY DEX BPL RTS ReadCtrlrs $F5 $F6 ReadCtrlrs $F6 $EA39 $F5 $EA39 $EA25 ReadCtrlrs ORctrlrRead $F5 $F6 ReadCtrlrs ORctrlrRead $F6 $EA52 $F5 $EA52 $EA25 ReadCtrlrs $00 $F7 $01 $F8 #$03 $F5,X $F1,X $F5,X $F5,X $F1,X $EA75

; ;VRAM fill routine ;

;this routine basically fills a specified place in VRAM with a desired value. ;when writing to name table memory, another value can be specified to fill the ;attribute table with. parameters are as follows: ;A is HI VRAM addr (LO VRAM addr is always 0) ;X is fill value ;Y is iteration count (256 written bytes per iteration). if A is $20 or ; greater (indicating name table VRAM), iteration count is always 4, ; and this data is used for attribute fill data. VRAMfill: STA $00 $EA86 STX $01 $EA88 STY $02 ;reset 2006's flip flop $EA8A LDA $2002; [NES] PPU status ;set PPU address increment to 1 $EA8D LDA $FF $EA8F AND #$fb $EA91 STA $2000; [NES] PPU setup #1 $EA94 STA $FF ;PPUaddrHI:=[$00] ;PPUaddrLO:=$00 $EA96 LDA $00 $EA98 STA $2006; $EA9B LDY #$00 $EA9D STY $2006;

[NES] VRAM address select [NES] VRAM address select

;if PPUaddr<$2000 then X:=[$02] else X:=4 $EAA0 LDX #$04 $EAA2 CMP #$20 $EAA4 BCS $EAA8; branch if more than or equal to $20 $EAA6 LDX $02 ;for i:=X downto 1 do Fill([$2007],A,256) $EAA8 LDY #$00 $EAAA LDA $01 $EAAC STA $2007; [NES] VRAM data $EAAF DEY $EAB0 BNE $EAAC $EAB2 DEX $EAB3 BNE $EAAC ;set up Y for next loop $EAB5 LDY $02 ;if PPUaddr>=$2000 then $EAB7 LDA $00 $EAB9 CMP #$20 $EABB BCC $EACF; branch if less than $20 ; PPUaddrHI:=[$00]+3 ; PPUaddrLO:=$C0 $EABD ADC #$02

$EABF $EAC2 $EAC4 ; for $EAC7 $EAC9 $EACC $EACD

STA $2006; LDA #$c0 STA $2006; I:=1 LDX STY DEX BNE

[NES] VRAM address select [NES] VRAM address select

to $40 do [$2007]:=[$02] #$40 $2007; [NES] VRAM data $EAC9

;restore X $EACF LDX $01 $EAD1 RTS ; ;CPU memory fill routine ; ;this routine simply fills CPU mapped memory with a given value. granularity ;is pages (256 bytes). parameters are as follows: ;A is fill value ;X is first page # ;Y is last page # MemFill: $EAD3 TXA $EAD4 STY $EAD6 CLC $EAD7 SBC $EAD9 TAX $EADA PLA $EADB LDY $EADD STY $EADF STA $EAE1 DEY $EAE2 BNE $EAE4 DEC $EAE6 INX $EAE7 BNE $EAE9 RTS PHA $01 $01 #$00 $00 ($00),Y $EADF $01 $EADF

; ; ; ;restore PPU reg's 0 & 5 from mem RstPPU05: LDA $2002; reset scroll register flip-flop $EAED LDA $FD $EAEF STA $2005; [NES] PPU scroll $EAF2 LDA $FC $EAF4 STA $2005; [NES] PPU scroll $EAF7 LDA $FF $EAF9 STA $2000; [NES] PPU setup #1 $EAFC RTS ;

$EAFD $EAFE $EAFF $EB00 $EB01 $EB03 $EB04 $EB06 $EB08 $EB09 $EB0A $EB0C $EB0E $EB10 $EB13 $EB15 $EB17 $EB19 $EB1B $EB1E $EB1F $EB20 $EB21 $EB22 $EB23 $EB24 $EB26 $EB28 $EB2A $EB2D $EB2F $EB30 $EB32 $EB33 $EB35 $EB38 $EB39 $EB3B $EB3D $EB3F $EB41 $EB43 $EB46 $EB48 $EB49 $EB4B $EB4C $EB4D $EB50 $EB51 $EB52 $EB53 $EB55 $EB57 $EB59 $EB5B $EB5C $EB5E

ASL TAY INY PLA STA PLA STA LDA TAX INY LDA STA STX JMP LDA AND STA ORA STA NOP NOP NOP NOP NOP NOP LDX LDA ORA STA LDY DEY BNE NOP LDY LDA LSR AND BEQ STA LDA ORA STA LDY DEY BNE NOP NOP LDA ROL ROL ROL AND ORA EOR STA DEX BPL LDY

$00 $01 ($00),Y ($00),Y $01 $00 ($0000) $FB #$f8 $FB #$05 $4016;

[NES] Joypad & I/O port for port #1

#$08 $FB #$04 $4016; #$0a $EB2F $FB $4017; A #$0f $EB62 $00,X $FB #$06 $4016; #$0a $EB48 $4017; A A A #$f0 $00,X #$ff $00,X $EB26 $FB

[NES] Joypad & I/O port for port #1

[NES] Joypad & I/O port for port #2

[NES] Joypad & I/O port for port #1

[NES] Joypad & I/O port for port #2

$EB60 $EB62 $EB65

ORA #$ff STY $4016; RTS

[NES] Joypad & I/O port for port #1

; ;CPU to PPU copy routine ; ;CPUtoPPUcpy is used for making data transfers between the PPU & CPU. ;arguments are passed in CPU registers, and also hardcoded as an immediate ;value after the call instruction. ;parameters ;---------;[RETaddr+1] is CPU xfer address (the 2 bytes immediately after the JSR inst.) ;X reg: # of 16-byte units to xfer to/from PPU ;Y reg: bits 8-15 of PPU xfer addr ;A reg: bottom part of PPU xfer addr, and xfer control. the bit layout ; is as follows: ; 0: ; 1: invert data/fill type xfer direction (0 = write to video mem)

; 2-3: xfer mode. note that on each iteration, 2 groups of 8 bytes ; are always xfered in/out of the PPU, but depending on the ; mode, 8 or 16 bytes will be xfered to/from CPU. The following ; chart describes how xfers to/from the PPU via the CPU are ; made. ; ; ; ; ; ; 1st 8 bytes 2nd 8 bytes --------------------0: CPU CPU+8 1: CPU fill bit 2: fill bit CPU 3: CPU ^ inv.bit CPU

; 4-7: bits 4-7 of PPU xfer addr. bits 0-3 are assumed 0. ;increment word at [$00] by 8. ;decrement byte at [$02]. Inc00by8: LDA #$08 Inc00byA: PHP $EB69 LDY #$00 $EB6B CLC $EB6C ADC $00 $EB6E STA $00 $EB70 LDA #$00 $EB72 ADC $01 $EB74 STA $01 $EB76 PLP $EB77 DEC $02

$EB79

RTS

;move 8 bytes pointed to by word[$00] to video buf. ;move direction is reversed if carry is set. Mov8BVid: LDX #$08 $EB7C BCS $EB88 $EB7E LDA ($00),Y $EB80 STA $2007; [NES] VRAM data $EB83 INY $EB84 DEX $EB85 BNE $EB7C $EB87 RTS $EB88 LDA $2007; [NES] VRAM data $EB8B STA ($00),Y $EB8D BCS $EB83 ;move the byte at [$03] to the video buffer 8 times. ;if carry is set, then make dummy reads. FillVidW8B: LDA $03 $EB91 LDX #$08 $EB93 BCS $EB9C $EB95 STA $2007; [NES] VRAM data $EB98 DEX $EB99 BNE $EB93 $EB9B RTS $EB9C LDA $2007; [NES] VRAM data $EB9F BCS $EB98 ;move 8 bytes pointed to by word[$00] to video buf. ;data is XORed with [$03] before being moved. Mov8BtoVid: LDX #$08 $EBA3 LDA $03 $EBA5 EOR ($00),Y $EBA7 STA $2007; [NES] VRAM data $EBAA INY $EBAB DEX $EBAC BNE $EBA3 $EBAE RTS ;load register variables into temporary memory CPUtoPPUcpy: STA $04 $EBB1 STX $02 $EBB3 STY $03 $EBB5 JSR GetHCparam; load hard-coded param into [$00]&[$01] ;set PPU address increment to 1 $EBB8 LDA $2002; [NES] PPU status $EBBB LDA $FF $EBBD AND #$fb $EBBF STA $FF $EBC1 STA $2000; [NES] PPU setup #1 ;PPUaddrHI:=[$03] ;PPUaddrLO:=[$04]and $F0 $EBC4 LDY $03 $EBC6 STY $2006; [NES] VRAM address select $EBC9 LDA $04 $EBCB AND #$f0 $EBCD STA $2006; [NES] VRAM address select

;[$03]:=Bit(0,[$04]) $EBD0 LDA #$00 $EBD2 STA $03 $EBD4 LDA $04 $EBD6 AND #$0f $EBD8 LSR A $EBD9 BCC $EBDD $EBDB DEC $03

0 if clear; -1 if set

;if Bit(1,[$04])then Temp:=[$2007] $EBDD LSR A $EBDE BCC $EBE3 $EBE0 LDX $2007; dummy read to validate internal read buffer ;case [$04]and $0C of $EBE3 TAY $EBE4 BEQ $EBFB; $EBE6 DEY $EBE7 BEQ $EC09; $EBE9 DEY $EBEA BEQ $EC15; $EBEC DEY; Y=0

00xx 01xx 02xx

;$0C: #2 plane copy (plane 1 is filled with same data, but can be inverted) $EBED JSR Mov8BtoVid $EBF0 LDY #$00 $EBF2 JSR Mov8BVid $EBF5 JSR Inc00by8 $EBF8 BNE $EBED $EBFA RTS ;$00: double plane copy $EBFB JSR Mov8BVid $EBFE JSR Mov8BVid $EC01 LDA #$10 $EC03 JSR Inc00byA $EC06 BNE $EBFB $EC08 RTS ;$04: #1 plane copy (plane 2 is filled with [$03]) $EC09 JSR Mov8BVid $EC0C JSR FillVidW8B $EC0F JSR Inc00by8 $EC12 BNE $EC09 $EC14 RTS ;$08: #2 plane copy (plane 1 is filled with [$03]) $EC15 JSR FillVidW8B $EC18 JSR Mov8BVid $EC1B JSR Inc00by8 $EC1E BNE $EC15 $EC20 RTS ; ; ; $EC21 $EC22 RTS LDY #$0b

$EC24 $EC26 $EC28 $EC2A $EC2C $EC2D $EC2F $EC30 $EC31 $EC32 $EC33 $EC35 $EC37 $EC39 $EC3B $EC3D $EC3F $EC41 $EC43 $EC45 $EC46 $EC47 $EC49 $EC4B $EC4D $EC4F $EC51 $EC53 $EC55 $EC56 $EC58 $EC5A $EC5C $EC5D $EC5F $EC60 $EC62 $EC64 $EC66 $EC68 $EC6A $EC6C $EC6E $EC70 $EC72 $EC74 $EC76 $EC78 $EC7A $EC7C $EC7D $EC7F $EC81 $EC83 $EC84 $EC86 $EC87 $EC88 $EC89 $EC8B

LDA STA LDA STA DEY LDA LSR LSR LSR LSR BEQ STA STA LDA AND BEQ STA LDY LDA TAX DEY LDA BEQ BPL LDX STX LDY LDA LSR AND BEQ LDA ROR STA INY LDA AND ORA STA LDY LDA STA LDA STA LDY STY LDA STA LDX TXA STA CMP BEQ CLC ADC TAX INY INY LDA STA

($00),Y $02 #$02 $03 ($00),Y A A A A $EC21 $04 $0C ($00),Y #$0f $EC21 $05 #$01 ($00),Y ($00),Y $EC4F $EC21 #$f4 $08 #$08 ($00),Y A #$08 $EC5C #$80 A $09 ($00),Y #$23 $09 $09 #$03 ($00),Y $0A $05 $07 #$00 $0B $04 $06 $08 ($02),Y #$f4 $EC87 #$08

$09 ($02),Y

$EC8D $EC8E $EC90 $EC92 $EC93 $EC95 $EC97 $EC99 $EC9B $EC9C $EC9E $ECA0 $ECA2 $ECA4 $ECA6 $ECA8 $ECAA $ECAB $ECAD $ECAF $ECB1 $ECB3 $ECB4 $ECB6 $ECB7 $ECB9 $ECBA $ECBC $ECBE $ECC0 $ECC2 $ECC4 $ECC5 $ECC7 $ECC9 $ECCB $ECCD $ECCF $ECD1 $ECD3 $ECD5 $ECD7 $ECD9 $ECDB $ECDD $ECDF $ECE0 $ECE2 $ECE3 $ECE5 $ECE7 $ECE8 $ECE9 $ECEB $ECEC $ECEE $ECF0 $ECF2 $ECF4

INY LDA STA INY INC DEC BNE LDA CLC ADC STA DEC BNE LDY LDA STA DEY LDA STA LDA STA CLC LDX DEY LDA CLC ADC STA LDA ADC STA DEX BNE INC LDY LDA BNE DEC LDY BIT BMI BVS LDA BIT BPL TYA STA DEY BIT BMI INY INY LDA CLC ADC STA DEC BNE RTS

$0A ($02),Y $0B $06 $EC7C $0A #$08 $0A $07 $EC76 #$07 ($00),Y $07 ($00),Y $08 #$00 $0A $0B ($00),Y $07 $07 #$00 $08 $08 $ECB7 $02 #$00 $08 $ECD3 $0A $07 $09 $ECF5 $ECF7 ($07),Y $0A $ECE0 ($02,X) $09 $ECE9 #$04 $02 $02 $0B $ECD9

$ECF5 $ECF7 $ECF8 $ECF9 $ECFB $ECFC $ECFD $ECFF $ED01 $ED03 $ED05 $ED07 $ED09 $ED0A $ED0B $ED0D $ED0E $ED10 $ED12 $ED13 $ED15 $ED17 $ED18 $ED19 $ED1B $ED1D $ED1F $ED20 $ED22 $ED24 $ED25 $ED27 $ED29 $ED2B $ED2D $ED2E $ED2F $ED31 $ED32 $ED34 $ED36

BVC TYA CLC ADC TAY DEY BIT BMI LDA EOR STA INC TYA CLC ADC TAY LDA STA DEY BIT BMI INY INY LDA BIT BPL TYA STA LDA CLC ADC STA DEC BNE TYA CLC ADC TAY DEC BNE RTS

$ED09 $0B $09 $ECD9 #$ff $0C $0C $0C $0C $04 $06 $09 $ED19 ($07),Y $0A $ED20 ($02,X) #$04 $02 $02 $06 $ED12 $0C $05 $ED09

24242424242424242424241712171D0E 170D1824282424242424242424242424 242424242424240F0A16121522240C18 16191E1D0E1B241D1624242424242424 24242424242424242424242424242424 24242424242424242424242424242424 24241D11121C24191B180D1E0C1D2412 1C24160A171E0F0A0C1D1E1B0E0D2424 24240A170D241C18150D240B22241712 171D0E170D18240C1827151D0D262424 2424181B240B2224181D110E1B240C18 16190A1722241E170D0E1B2424242424 242415120C0E171C0E24180F24171217 1D0E170D18240C1827151D0D26262424 ;a disk-related subroutine, which somehow ended up all the way out here...

StartMotor: $EE19 STA $EE1C AND $EE1E STA $EE20 STA $EE23 RTS

ORA #$01 $4025 #$fd $FA $4025

; ;Reset vector ; ;disable interrupts (just in case resetting the CPU doesn't!) Reset: SEI ;set up $EE25 $EE27 $EE2A PPU LDA STA STA ctrl reg #1 #$10 $2000; [NES] PPU setup #1 $FF

;clear decimal flag (in case this code is executed on a CPU with dec. mode) $EE2C CLD ;set up $EE2D $EE2F $EE31 PPU LDA STA STA ctrl reg #2 (disable playfield & objects) #$06 $FE $2001; [NES] PPU setup #2 loop count = 2 iterations [NES] PPU status branch if VBL has not been reached exit loop when X = 0 disable timer interrupt disable sound & disk I/O enable sound & disk I/O

;wait at least 1 frame $EE34 LDX #$02; $EE36 LDA $2002; $EE39 BPL $EE36; $EE3B DEX $EE3C BNE $EE36; $EE3E $EE41 $EE44 $EE46 $EE49 $EE4B $EE4D $EE4F $EE52 $EE54 $EE56 $EE59 $EE5B $EE5D $EE60 $EE63 $EE65 $EE68 $EE6A $EE6D $EE6F $EE72 $EE74 $EE77 $EE79 STX STX LDA STA STX STX STX STX LDA STA STA LDA STA STA STX LDA STA LDA STA LDA STA LDA STA LDX TXS $4022; $4023; #$83 $4023; $FD $FC $FB $4016; #$2e $FA $4025 #$ff $F9 $4026 $4010; #$c0 $4017; #$0f $4015; #$80 $4080 #$e8 $408A #$ff;

[NES] Joypad & I/O port for port #1

[NES] Audio - DPCM control [NES] Joypad & I/O port for port #2 [NES] IRQ status / Sound enable

set up stack

$EE7A $EE7C $EE7F $EE81

LDA STA LDA STA

#$c0 $0100 #$80 $0101

;if ([$102]=$35)and(([$103]=$53)or([$103]=$AC)) then ; [$103]:=$53 ; CALL RstPPU05 ; CLI ; JMP [$DFFC] $EE84 LDA $0102 $EE87 CMP #$35 $EE89 BNE $EEA2 $EE8B LDA $0103 $EE8E CMP #$53 $EE90 BEQ $EE9B $EE92 CMP #$ac $EE94 BNE $EEA2 $EE96 LDA #$53 $EE98 STA $0103 $EE9B JSR RstPPU05 $EE9E CLI; enable interrupts $EE9F JMP ($DFFC) ;for I:=$F8 $EEA2 LDA $EEA4 LDX $EEA6 STA $EEA8 DEX $EEA9 BNE downto $01 do [I]:=$00 #$00 #$f8 $00,X $EEA6

;[$300]:=$7D ;[$301]:=$00 ;[$302]:=$FF $EEAB STA $0301 $EEAE LDA #$7d $EEB0 STA $0300 $EEB3 LDA #$ff $EEB5 STA $0302 ;if Ctrlr1 = $30 then ; [$0102]:=0 ; JMP $F4CC $EEB8 JSR GetCtrlrSts $EEBB LDA $F7; read ctrlr 1 buttons $EEBD CMP #$30; test if only select & start pressed $EEBF BNE $EEC9 $EEC1 LDA #$00 $EEC3 STA $0102 $EEC6 JMP $F4CC $EEC9 $EECC $EECF $EED1 $EED3 $EED5 $EED7 $EED9 $EEDB JSR JSR LDA STA LDA STA LDA STA LDA InitGfx $F0FD #$4a $A1 #$30 $B1 #$e4 $83 #$a9

$EEDD

STA $FC

;test if disk inserted $EEDF LDA $4032 $EEE2 AND #$01 $EEE4 BEQ $EEEA $EEE6 $EEE8 $EEEA $EEEC $EEEE $EEF1 $EEF4 $EEF6 $EEF8 $EEFA $EEFC $EEFE $EF01 $EF04 $EF07 $EF0A $EF0C $EF0E $EF11 $EF14 $EF17 $EF19 $EF1C $EF1E $EF21 $EF23 $EF25 $EF27 $EF29 $EF2C $EF2E $EF30 $EF33 $EF36 $EF38 $EF3A $EF3C $EF3E $EF40 $EF42 $EF44 $EF46 $EF49 $EF4C $EF51 $EF56 $EF59 $EF60 $EF62 $EF65 $EF67 $EF6A $EF6C LDA STA LDA STA JSR JSR LDA CMP BNE LDA STA JSR JSR JSR JSR LDX LDY JSR JSR JSR LDX JSR LDX JSR LDA STA LDA STA JSR LDA STA JSR LDA AND BNE LDA BEQ LDA STA LDA BNE JSR JSR JSR JSR JSR JSR BNE JSR BEQ JSR LDA STA #$04 $E1 #$34 $90 $F376 VINTwait $90 #$32 $EEFE #$01 $E1 $F0B4 RstPPU05 EnPfOBJ $EFE8 #$60 #$20 RndmNbrGen $F143 $F342 #$00 $F1E5 #$10 $F1E5 #$c0 $00 #$00 $01 $EC22 #$d0 $00 $EC22 $4032 #$01 $EEEA $FC $EF42 #$01 $FC $90 $EEEE DisOBJs VINTwait PPUdataPrsr,$EFFF PPUdataPrsr,$F01C RstPPU05 LoadFiles,$EFF5,$EFF5;load the FDS disk boot files $EF6C $F431 $EFAF $F5FB #$20 $23

$EF6E $EF71 $EF74 $EF77 $EF7A $EF7D $EF7F $EF81 $EF83 $EF85 $EF87 $EF89 $EF8B $EF8E $EF91 $EF94 $EF97 $EF9A $EF9D $EF9F $EFA1 $EFA3 $EFA5 $EFA8 $EFAA $EFAC $EFAF $EFB1 $EFB3 $EFB6 $EFB9 $EFBC $EFBE $EFBF $EFC0 $EFC2 $EFC4 $EFC6 $EFC9 $EFCB $EFCD $EFCF $EFD2 $EFD4 $EFD7 $EFDA $EFDC $EFDF $EFE1 $EFE3 $EFE5 $EFE8 $EFEB $EFED $EFEF $EFF1

JSR JSR JSR JSR JSR LDA STA LDA BEQ LDA STA DEC JSR JSR JSR JSR JSR JSR LDA STA LDA BNE LDA AND BEQ JMP LDA STA JSR JSR JSR LDX INX INX CPX BCS STX JSR LDA BNE LDA STA LDA STA JSR LDY JSR LDA STA STA JMP JSR LDX LDA LDY JSR

InitGfx $F0E1 $F0E7 $F0ED $F179 #$10 $A3 $22 $EF8B #$01 $83 $21 $F376 VINTwait $E86A RstPPU05 EnPF $EFE8 #$02 $E1 $A3 $EF8B $4032 #$01 $EFA5 Reset #$20 $A2 VINTwait RstPPU05 EnPF $FC #$b0 $EFC6 $FC $EFE8 $A2 $EFB3 #$35 $0102 #$ac $0103 DisPF #$07 $F48C #$00 $FD $FC $EE9B $FF5C #$80 #$9f #$bf $E9D3

$EFF4

RTS

FFFFFFFFFFFF0000FFFF $EFFF $F004 $F01C 21A6 54 24 FF 21A6 14 19150E0A1C0E241C0E1D240D121C14240C0A1B0D FF 21A6 0E 1718202415180A0D121710262626 FF

0D121C14241C0E1D0B0A1D1D0E1B2224 0A250B241C120D0E0D121C1424171826 21A6140D121C14241D1B181E0B150E24 240E1B1B260200FF20E810191B0A1624 0C1B0A16242424242418142168041918 1B1D3F00080F200F0F0F0F0F0F2BC050 002BD07055FF ;$F094 80B80000000000001000320000000100 80B800F000000000000132180000FF00 $F0B4 $F0B6 $F0B8 $F0BA $F0BC $F0BE $F0C0 $F0C2 $F0C4 $F0C5 $F0C7 $F0C8 $F0CA $F0CB $F0CD $F0D0 $F0D3 $F0D5 $F0D7 $F0DC $F0DE $F0E0 $F0E1 $F0E6 $F0E7 $F0EC $F0ED LDA BEQ DEC BNE LDA STA LDX BEQ DEX BEQ DEX BEQ DEX BEQ JSR JSR LDA BNE JSR LDA STA RTS $FC $F0C0 $FC $F0C0 #$10 $94 $94 $F0CD $F0E1 $F0E7 $F0ED $E9C8 $E86A $92 $F0F3 PPUdataPrsr,$EFFF #$40 $92

JSR PPUdataPrsr,$F716 RTS JSR PPUdataPrsr,$F723 RTS JSR PPUdataPrsr,$F72C

$F0F2 $F0F3 $F0F5 $F0F7 $F0FC

RTS CMP #$2e BNE $F0FC JSR PPUdataPrsr,$F004 RTS

; ;fill $0200-$02FF with $F4 $F0FD LDA #$f4 $F0FF LDX #$02 $F101 LDY #$02 $F103 JSR MemFill ;move data ;for I:=0 to $1F do [$C0+I]:=[$F094+I] $F106 LDY #$20 $F108 LDA $F093,Y $F10B STA $00BF,Y $F10E DEY $F10F BNE $F108 ;fill $0230-$02FF with random data ;for I:=$0230 to $02FF do [I]:=Random(256) $F111 LDA #$d0; loop count $F113 STA $60; load random number target with any data $F115 STA $01; save loop count in [$01] $F117 LDY #$02 $F119 LDX #$60 $F11B JSR RndmNbrGen; [$60] and [$61] are random number target $F11E LDA $60; get random number $F120 LDX $01; load loop count (and index) $F122 STA $022F,X; write out random # $F125 DEX $F126 STX $01; save loop count $F128 BNE $F117 ;fill every 4th byte in random data area with $33 ;for I:=0 to $33 do [I*4+$0231]:=$18 $F12A LDA #$18 $F12C LDX #$d0 $F12E STA $022D,X $F131 DEX $F132 DEX $F133 DEX $F134 DEX $F135 BNE $F12E ;and & or every 4th byte in random data ;for I:=0 to $33 do [I*4+$0232]:=([I*4+$0232]-1)and $03 or $20 $F137 LDX #$d0 $F139 STX $24 $F13B JSR $F156 $F13E CPX #$d0 $F140 BNE $F13B $F142 RTS

; $F143 $F145 $F147 $F149 $F14B $F14D $F150 $F151 $F152 $F153 $F154 LDA BNE LDA STA LDX DEC DEX DEX DEX DEX BNE $84 $F156 #$04 $84 #$d0 $022C,X

$F14D

;for I:=0 to 3 do ; [$022E+X]:=([$022E+X]-1)and $03 or $20 ; X-=4 ; if X=0 then X:=$d0 ;end $F156 LDY #$04 $F158 LDX $24 $F15A DEC $022E,X $F15D LDA #$03 $F15F AND $022E,X $F162 ORA #$20 $F164 STA $022E,X $F167 DEX $F168 DEX $F169 DEX $F16A DEX $F16B BNE $F16F $F16D LDX #$d0 $F16F STX $24 $F171 DEY $F172 BNE $F158 $F174 RTS $F175 $F179 $F17B $F17E $F181 $F182 $F184 $F186 $F188 $F18A $F18C $F18D $F18E $F18F $F190 $F192 $F194 $F196 $F198 $F19A DB 01 02 07 08 LDY LDA STA DEY BNE LDA AND STA LDA LSR LSR LSR LSR STA CMP BEQ LDY LDA STA #$18 $F04D,Y $003F,Y $F17B $23 #$0f $56 $23 A A A A $55 #$02 $F1BD #$0e #$24 $0042,Y

$F19D $F19E $F1A0 $F1A2 $F1A4 $F1A5 $F1A7 $F1AA $F1AC $F1AD $F1AE $F1AF $F1B0 $F1B1 $F1B3 $F1B4 $F1B7 $F1BA $F1BB $F1BD $F1C2

DEY BNE LDY LDA DEY BEQ CMP BNE TYA ASL ASL ASL TAX LDY DEX LDA STA DEY BPL JSR RTS

$F19A #$05 $23 $F1BD $F174,Y $F1A4 A A A #$07 $F02E,X $0043,Y $F1B3 PPUdataPrsr,$0040

; ;copy font bitmaps into PPU memory ;src:CPU[$E001] dest:PPU[$1000] tiles:41 (41*8 bytes, 1st plane is inverted) LoadFonts: LDA #$0d $F1C5 LDY #$10 $F1C7 LDX #$29 $F1C9 JSR CPUtoPPUcpy,$E001 ;copy inverted font bitmaps from PPU mem to [$0400] ;src:PPU[$1000] dest:CPU[$0400] tiles:41 (41*8 bytes) $F1CE LDA #$06 $F1D0 LDY #$10 $F1D2 LDX #$29 $F1D4 JSR CPUtoPPUcpy,$0400 ;copy back fonts & set first plane to all 1's ;src:CPU[$0400] dest:PPU[$1000] tiles:41 (41*8 bytes, 1st plane is all 1's) $F1D9 LDA #$09 $F1DB LDY #$10 $F1DD LDX #$29 $F1DF JSR CPUtoPPUcpy,$0400 $F1E4 RTS ; $F1E5 $F1E8 $F1EB $F1EE $F1F1 $F1F2 $F1F4 $F1F6 $F1F8 JSR JSR JSR JSR RTS LDA BNE LDA BNE $F1F2 $F2EC $F273 $F2C6

$20,X $F227 $C0,X $F227

$F1FA $F1FC $F1FE $F200 $F202 $F204 $F206 $F208 $F209 $F20B $F20D $F20F $F211 $F213 $F215 $F217 $F219 $F21B $F21D $F21F $F221 $F223 $F225 $F227 $F228 $F22A $F22C $F22E $F230 $F232 $F234 $F236 $F238 $F23A $F23C $F23D $F23F $F241 $F243 $F245 $F247 $F249 $F24B $F24D $F24F $F251 $F253 $F255 $F257 $F259 $F25A $F25C $F25E $F260

LDA BNE LDA BNE LDA AND STA TXA BNE LDA BNE LDA BEQ LDA BNE LDA CMP BCC LDA STA STA LDA STA RTS LDA BEQ LDA BNE LDA CMP BCS LDA STA STA RTS LDA BNE LDA CMP BCC LDA CMP BCC LDA STA LDA STA LDA STA RTS LDA LDY BEQ CMP

$B0 $F227 $81,X $F227 $62,X #$3c $81,X $F228 $B2 $F236 $D0 $F23D $22 $F21D $C3 #$78 $F24D #$00 $C8,X $CF,X #$ff $CE,X

$C0 $F25A $22 $F21D $63,X #$80 $F24D #$00 $CF,X $CE,X

$C8 $F247 $63,X #$c0 $F21D $64,X #$80 $F236 #$10 $C8,X #$00 $CF,X #$01 $CE,X

$64,X $C8 $F264 #$40

$F262 $F264 $F266 $F268 $F26A $F26C $F26E $F270 $F272 $F273 $F275 $F277 $F279 $F27A $F27C $F27E $F280 $F282 $F284 $F286 $F287 $F289 $F28B $F28D $F28F $F291 $F293 $F295 $F296 $F298 $F29A $F29C $F29E $F2A0 $F2A2 $F2A4 $F2A6 $F2A8 $F2AA $F2AB $F2AD $F2AF $F2B1 $F2B3 $F2B5 $F2B6 $F2B8 $F2BA $F2BC $F2BE $F2C0 $F2C2 $F2C4 $F2C6

BCC CMP BCC LDA STA LDA STA STA RTS LDA BEQ BMI CLC LDA ADC STA LDA ADC STA CLC LDA ADC STA LDA ADC CMP BCC TXA BNE LDA AND STA LDA STA LDA STA LDA STA RTS DEC LDA STA LDA STA RTS STA LDA STA LDA STA LDA STA BNE LDA

$F24D #$c0 $F236 #$40 $CF,X #$00 $CE,X $C8,X

$20,X $F2AA $F2AB #$30 $CD,X $CD,X #$00 $CC,X $CC,X $CD,X $C2,X $C2,X $CC,X $C1,X #$b8 $F2A4 $F2B6 $60,X #$30 $81,X #$00 $20,X #$b8 $C1,X #$03 $C5,X

$20,X #$fd $CC,X #$00 $CD,X

$C8,X #$01 $CE,X #$c0 $CF,X #$ff $81,X $F29E $B0

$F2C8 $F2CA $F2CC $F2CE $F2D0 $F2D2 $F2D4 $F2D6 $F2D8 $F2DA $F2DC $F2DF $F2E1 $F2E2 $F2E3 $F2E5 $F2E7 $F2E8 $F2EC $F2EE $F2F0 $F2F1 $F2F3 $F2F5 $F2F7 $F2F9 $F2FB $F2FD $F2FF $F301 $F303 $F305 $F307 $F309 $F30B $F30D $F30F $F311 $F313 $F315 $F317 $F318 $F319 $F31B $F31C $F31E $F320 $F322 $F324 $F327 $F329 $F32A $F32C $F32E

BNE LDA BNE LDA BEQ LDA ORA AND STA LDY LDA STA INX DEY BNE STY RTS

$F2E7 $A1,X $F2E7 $C0,X $F2E7 $62,X #$10 #$3c $81,X #$10 $F094,X $C0,X $F2DC $B0,X

DB 00 02 01 02 LDA BNE CLC LDA ADC STA LDA ADC LDY CPY BCS CMP BCC CPY BCS LDA AND ORA STA LDA STA STA LSR LSR AND TAY LDA ORA BNE LDY LDA STA RTS $C0,X $F329 $CF,X $C4,X $C4,X $CE,X $C3,X $B0 #$20 $F315 #$f8 $F32A #$1f $F315 $60,X #$2f #$06 $A1,X #$80 $C0,X $C3,X A A #$03 $CE,X $CF,X $F324 #$01 $F2E8,Y $C5,X

CMP #$78 BNE $F315 CPX $22

$F330 $F332 $F334 $F336 $F338 $F33A $F33C $F33E $F340 $F342 $F344 $F346 $F348 $F34A $F34C $F34D $F34F $F351 $F353 $F355 $F357 $F359 $F35B $F35D $F35F $F361 $F363 $F365 $F367 $F369 $F36B $F36D $F36E $F376 $F378 $F37A $F37C $F37E $F380 $F382 $F384 $F386 $F388 $F38A $F38C $F38E $F390 $F392 $F394 $F396 $F398 $F39A $F39C $F39E $F3A0 $F3A2

BNE LDY BNE LDY STY STY LDY STY BNE LDA BNE LDA ORA BNE CLC LDA ADC CMP BCC STA LDA STA STA LDA STA STA LDA STA STA LDA STA RTS

$F315 $20,X $F315 #$00 $CE,X $CF,X #$80 $20,X $F315 $B0 $F36D $C0 $D0 $F36D $C3 #$19 $D3 $F36D $D3 #$02 $CE $DE #$00 $CF $DF #$10 $C8 $D8 #$30 $B0

DB 2A 0A 25 05 21 01 27 16 LDY LDA BNE LDA BNE LDX LDA CMP BCS LDA LDY BNE LDA LDY CPY BCS LDA STA LDA STA CPX LDX BCC #$08 $83 $F3C8 $93 $F3EF #$00 $C1,X #$a4 $F39E #$20 $B2 $F39C #$08 $65 #$18 $F39C #$08 $B2 #$20 $83,X #$10 #$10 $F382

$F3A4 $F3A6 $F3A8 $F3AA $F3AC $F3AE $F3B0 $F3B2 $F3B4 $F3B6 $F3B8 $F3BA $F3BC $F3BE $F3C0 $F3C2 $F3C7 $F3C8 $F3CB $F3CE $F3CF $F3D1 $F3D3 $F3D5 $F3D7 $F3D8 $F3DB $F3DD $F3E0 $F3E2 $F3E4 $F3E6 $F3E8 $F3EA $F3EC $F3EF $F3F2 $F3F5 $F3F6 $F3F8

LDA BEQ LDA BNE LDA STA LDX LDA CMP BNE LDX STX LDA LDX LDY JSR RTS LDA STA DEY BNE INC LDA AND TAY LDA STA LDA STA LDY LDA BNE LDY STY JMP LDA STA DEY BNE BEQ

$22 $F3C7 $82 $F3C7 #$08 $82 #$0f $47 #$0f $F3BA #$16 $47 #$3f #$08 #$08 $E8D2,$0040

$F634,Y $003F,Y $F3C8 $21 $21 #$06 $F36E,Y $42 $F36F,Y $43 #$00 $B2 $F3EA #$10 $22 $F3BC $F63F,Y $003F,Y $F3EF $F3EA

; ;initialize graphics ; ;this subroutine copies pattern tables from ROM into the VRAM, and also ;sets up the name & palette tables. ;entry point InitGfx: JSR DisPfOBJ; disable objects & playfield for video xfers

;src:CPU[$F735] dest:PPU[$1300] xfer:88 tiles $F3FD LDA #$00 $F3FF LDX #$58 $F401 LDY #$13

$F403

JSR CPUtoPPUcpy,$F735

;src:CPU[$FCA5] dest:PPU[$0000] xfer:25 tiles $F408 LDA #$00 $F40A LDX #$19 $F40C LDY #$00 $F40E JSR CPUtoPPUcpy,$FCA5 $F413 JSR LoadFonts; load fonts from ROM into video mem

;dest:PPU[$2000] NTfillVal:=$6D ATfillVal:=$aa $F416 LDA #$20 $F418 LDX #$6d $F41A LDY #$aa $F41C JSR VRAMfill ;dest:PPU[$2800] NTfillVal:=$6D ATfillVal:=$aa $F41F LDA #$28 $F421 LDX #$6d $F423 LDY #$aa $F425 JSR VRAMfill $F428 $F42B $F430 JSR VINTwait JSR PPUdataPrsr,InitNT; initialize name table RTS

; ; ; $F431 $F434 $F436 $F439 $F43C $F43F $F444 $F446 $F448 $F44A $F44D $F44F $F451 $F453 $F456 $F459 $F45B $F45E $F460 $F463 $F466 $F468 $F46B $F46E $F470 $F471 $F473 $F475 $F478 JSR LDY JSR JSR JSR JSR LDA LDX LDY JSR LDA AND STA STA LDX LDX STX LDA STA LDA LDY LDA CMP BNE INY CPY BNE STX STY DisPfOBJ #$03 $F48C LoadFonts VINTwait PPUdataPrsr,$F080 #$20 #$28 #$00 VRAMfill $FF #$fb $FF $2000; [NES] $2002; [NES] #$28 $2006; [NES] #$00 $2006; [NES] $2007; [NES] #$00 $2007; [NES] $ED37,Y $F483 #$e0 $F468 $2006; $2006;

PPU setup #1 PPU status VRAM address select VRAM address select VRAM data VRAM data

[NES] VRAM address select [NES] VRAM address select

$F47B $F47D $F480 $F481 $F483 $F484 $F48C $F48E $F491 $F493 $F494 $F495 $F497 $F499 $F49B $F49D $F49F $F4A1 $F4A6 $F4A8 $F4AA $F4AC $F4B1 $F4B3 $F4B4 $F4B6 $F4B9 $F4BC $F4BE $F4C1 $F4C4 $F4C5 $F4C7 $F4C9 $F4CB $F4CC $F4CE $F4D0 $F4D2 $F4D5 $F4D8 $F4DD $F4E0 $F4E2 $F4E4 $F4E6 $F4E8 $F4ED $F4EF $F4F1 $F4F3 $F4F8 $F4FA $F4FC

LDA #$24 STA $2007; INY BNE $F47B RTS

[NES] VRAM data

DB 02 30 10 29 32 00 29 10 LDX LDA STA DEY DEX BPL LDA STA LDA LDX LDY JSR LDA LDX LDY JSR LDY CLC LDA ADC STA LDA ADC STA DEY BPL DEC BNE RTS LDA LDX LDY JSR JSR JSR JSR BNE LDA LDX LDY JSR LDA LDX LDY JSR LDA LDX LDY #$03 $F484,Y $07,X $F48E #$29 $0B $07 #$01 $09 CPUtoPPUcpy,$0010 $08 #$01 $0A CPUtoPPUcpy,$0010 #$01 #$10 $0007,Y $0007,Y #$00 $0009,Y $0009,Y $F4B3 $0B $F49B

#$20 #$24 #$00 VRAMfill VINTwait PPUdataPrsr,$F066 $F5FB $F527 #$00 #$00 #$00 CPUtoPPUcpy,$C000 #$00 #$00 #$10 CPUtoPPUcpy,$D000 #$02 #$00 #$00

$F4FE $F503 $F505 $F507 $F509 $F50E $F510 $F512 $F514 $F516 $F518 $F51A $F51C $F51F $F521 $F523 $F525 $F527 $F529 $F52B $F52D $F52F $F530 $F532 $F535 $F536 $F537 $F538 $F539 $F53A $F53B $F53E $F540 $F541 $F543 $F545 $F547 $F549 $F54E $F551 $F554 $F556 $F558 $F55A $F55F $F562 $F565 $F568

JSR LDA LDX LDY JSR LDA STA LDY STY LDX LDA ADC JSR BEQ LDA AND STA LDA STA LDY LDA TAX AND STA DEY TXA LSR LSR LSR LSR STA LDA DEY BPL LDA LDX LDY JSR JSR JSR LDA CMP BNE JSR JSR JSR JSR JMP

CPUtoPPUcpy,$C000 #$02 #$00 #$10 CPUtoPPUcpy,$D000 #$C0 $01 #$00 $00 #$20 #$7f #$02 $F61B $F54E $01 #$03 $01 #$11 $0B #$03 $00 #$0F $0007,Y A A A A $0007,Y $01 $F52F #$20 #$f4 #$05 $E8D2,$0007 LoadFonts GetCtrlrSts $F7 #$81 $F5B8 PPUdataPrsr,$F56B VINTwait RstPPU05 EnPF $F568

20E7 11 020C03032412171D0E1B170A15241B1816 2163 19 191B18101B0A160E0D240B22241D0A140A18241C0A200A1718 21A3 19 1712171D0E170D18240C1827151D0D26240D0E1F2617182602 FF $F5B8 $F5BA $F5BC $F5BE LDA #$01 STA $0F LDA #$ff CLC

$F5BF $F5C0 $F5C1 $F5C4 $F5C7 $F5CA $F5CD $F5CF $F5D1 $F5D2 $F5D3 $F5D6 $F5D7 $F5D8 $F5D9 $F5DB $F5DD $F5E0 $F5E2 $F5E4 $F5E5 $F5E7 $F5E8 $F5EA $F5EB $F5ED $F5EF $F5F1 $F5F3 $F5F8 $F5FB $F5FD $F5FF $F601 $F602 $F604 $F606 $F608 $F609 $F60C $F60D $F60F $F611 $F613 $F615 $F617 $F61A $F61B $F61D $F61F $F621 $F623 $F625 $F626 $F628 $F62A

PHA PHP JSR JSR JSR JSR DEC BNE PLP PLA STA ROL PHA PHP LDA STA LDA LDX LDY ASL BCS DEY STY DEX BPL LDA LDX LDY JSR JMP LDA LDX STX PHA STA LDY STY CLV JSR PLA STA STY LDX LDA ADC JSR RTS STX LDA BVS STA INC DEY BNE INC DEX

VINTwait $E86A RstPPU05 EnPF $0F $F5DD $4026 A #$19 $0F $4033 #$07 #$01 A $F5E8 $07,X $F5E2 #$21 #$70 #$08 $E8D2,$0007 $F5C1 #$60 #$80 $03 $01 #$00 $00 $F61B $01 $00 $03 #$7f #$02 $F61B

$02 $02 $F62E ($00),Y $02 $F61D $01

$F62B $F62D $F62E $F630 $F632 $F634

BNE $F61B RTS CMP ($00),Y BEQ $F623 STY $00 RTS

;$F635 0F 30 27 16 0F 10 00 16 ;PPU processor data InitNT: 3F08 18 0F21010F0F0002010F2716010F27301A0F0F010F0F0F0F0F 20E4 02 6E73 20E6 54 77 20FA 02 787C 2104 02 6F74 2106 54 24 211A 02 797D 2124 C5 70 213B C5 70 2125 C5 24 213A C5 24 21C4 02 7175 21C6 54 24 21DA 02 7A7E 21E4 02 7276 21E6 54 77 21FA 02 7B7F 2126 14 3034383B3F2424474B2424242424245D61242428 2146 14 3135323C404346484C4E513C54575A5E6265686B 2166 14 3236393D414432494D4F523D55585B5F6366696C 2186 14 33373A3E4245334A4550533E56595C6064676A24 21A6 54 24 220F C4 83 2210 C4 84 228F 02 8586 23E0 50 FF 23F0 48 AF FF 2040 60 80 2020 60 81 2000 60 81 FF 2340 60 80 2360 60 81 FF 2380 60 82 23A0 60 82 FF ;PATTERN TABLE DATA ;$F735 FFC0C0C0C0C0C0C080BFBFBFBFBFBFBF C0C0C0C0C0C0C0C0BFBFBFBFBFBFBFBF C0C0C0C0C0C0C0C0BFBFBFBFBFBFBFBF

C0C0C0FFFFFFFFFFBFBFBFFFFFFFFFFF 7F7F3F3F1F1F0F0FFFFFFFFFFFFFFFFF 078783C3C1E1E0F0FF7F7FBFBFDFDFEF F0F8F8FCFCFEFEFFEFF7F7FBFBFDFDFE FFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFF FFC0C0C0C0C0C0C080BFBFBFBFBFBFBF 0000000000000000FFFFFFFFFFFFFFFF 008080FFFFFFFFFFFF7F7FFFFFFFFFFF FFE0E0E0E0FFFFFFC0DFDFDFDFFFFFFF FFE0E0E0E0E0E0E0C0DFDFDFDFDFDFDF E0E0E0E0E0E0E0E0DFDFDFDFDFDFDFDF E0E0E0FFFFFFFFFFDFDFDFFFFFFFFFFF FF7F7F7F7FFFFFFF7FFFFFFFFFFFFFFF FF7070707070707060EFEFEFEFEFEFEF 7070707070707070EFEFEFEFEFEFEFEF 707070FFFFFFFFFFEFEFEFFFFFFFFFFF FF20000F1F1F3F3F20DFFFFEFFFFFFFF 3F3F3F3F3F3F3F3FFFFFFFFFFFFFFFFF 3F3F3FFFFFFFFFFFFFFFFFFFFFFFFFFF FF7F1F078181C0C07F9FE7FB7F7FBFBF FFFFFFFFFFF0F0F0FFFFFFFFE0EFEF0F 8090F0F0F0F0F0F07F6FEFEFEFEFEFEF F0F0F0F0F0F0F0F0EFEFEFEFEFEFEFEF F0F0F0FFFFFFFFFFEFEFEFFFFFFFFFFF FFFFFFFFFF3F3F3FFFFFFFFF3FFFFFC7 07073F3F3E3E3C3CFFFFFFFEFDFDFBFB 3C3C3C3C3C3E3E3EFBFBFBFBFBFDFDFF FFE0800F1F1F1F3FE09F7FFFFFFFFFC0 00001F1F1F1F1F1FFFFFFFFFFFFFFFEF 0F80E0FFFFFFFFFFF0FFFFFFFFFFFFFF FF3F0FC7E1E1E0E03FCFF7BBDFDFDF1F 0000FFFFE0E1E1C3FFFFFFC0DFDFDFBF 870F3FFFFFFFFFFF7FFFFFFFFFFFFFFF FF40001C3F3F7F7F40BFFFFFFEFEFFFF 7F7F7F7F7F7F7F7FFFFFFFFFFFFFFFFF 7F7F7FFFFFFFFFFFFFFFFFFFFFFFFFFF FFFF3F0F03038181FF3FCFF7FFFF7F7F 81818181818181817F7F7F7F7F7F7F7F 818181FFFFFFFFFF7F7F7FFFFFFFFFFF FFFCF0E0E0C0C0C0FCF3EFDFDFBFBFBF C0C0C0C0C0C0E0E0BFBFBFBFBFBFDFDF F0F0FCFFFFFFFFFFEFFFFFFFFFFFFFFF FFFEFEFEFEFEFEFEFCFDFDFDFDFDFDFD FE0602007CFEFEFE05F9FDFFFBFDFDFD FEFEFEFEFEFEFC78FDFDFDFDFDFD7B87 020206FFFFFFFFFFFDFDFDFFFFFFFFFF FF0707070707070707FFFFFFFFFFFFFF 0707070707070707FFFFFFFFFFFFFEFE 0707070707070707FEFEFEFEFEFFFFFF 070707FFFFFFFFFFFFFFFFFFFFFFFFFF FFF8E0C183870707F8E7DFBF7F7FFFFF 07070707078783C3FFFFFFFFFF7F7FBD E1E0F8FFFFFFFFFFDEFFFFFFFFFFFFFF FF0F03C1F0F8F8F80FF3FDFEEFF7F7F7 F8F8F8F8F8F8F0E0F7F7F7F7F7F7EFDF C1030FFFFFFFFFFF3FFFFFFFFFFFFFFF FFFFFFFF7F7F3F3FFFFFFFFFFFFFFFFF 3F3F3F3F3F7F7FFFFFFFFFFFFFFFFFFF 00000000000000000000000000000000 00000103060C18100003060C1933276F

30202060404040404F5EDC9CB9B9B9B9 4040404040404040B9B9B9B9B9B9B9B9 4040404060202020B9B9B9B99CDE5F5F 10000000000000006F3F3F1F0E070300 007FC00000000000FF803FFFFFF0C08F 071F3F7F7FFFFFFF3F7FFFFFFFFFFFFF FFFFFF7F7F3F1F07FFFFFFFFFFFFFFFF 100F000000000000EFF0FFFFFF0080FF 00FF000000000000FF00FFFFFF0000FF 00FA000000000000FF05FFFFFF3F1FFF E0F9FCFEFEFFFFFFFFFEFFFFFFFFFFFF FFFFFFFEFEFCF9E3FFFFFFFFFFFFFEFC 0EF8000000000000F107FFFFFC0001FF 000000000000000000C0E0F0F8FCFCFE 0000808040404040FEFA7B79B9B9B9B9 40404040C0808000B9B9B9B9397372E2 0000000000000000E6C48C183060C000 00FF00FF00FF00FFFFFFFFFFFFFFFFFF FF00FFFFFF00FFFFFFFFFFFFFFFFFFFF FFFF00FFFFFFFFFFFFFFFFFFFFFFFFFF 06060606060606000000000000000000 60606060606060000000000000000000 1F5F505757501F002020AFA7A7AF603F F8FA0AEAEA0AF8000404F5E5E5F506FC ;$FCA5 000000000000031F00000003030F0002 3F1F0F0720707020041E000001030F1F 00040F1F0F0C00000F072F3F3F1C1800 000000000000F0F800000080E0F0F048 F8FCF8E010103226489C0800F0FCFCF8 7CF8F8FCFC780000F8F8F8FCFC7C1C38 00000000000000070000000007071F01 3F7F3F1F0F01C2C204083D0000070F1F C6071F1F0F0F00003F7F1D1F0F0F070F 00000000000000E00000000000C0E0E0 F0F0F8F0C00000009090381000E0F0F8 0C1CFCF8F8780000F0E0E0F4FE7E0200 000000000000031F00000003030F0002 3F1F0F0700010113041E000003070F0F 1F1F0F07070300000F0D0F0707030001 000000000000F0F800000080E0F0F048 F8FCF8E0800000C0489C0800E0F0F8F8 80C0F0F0F0E00000F8301030F0E0E0E0 001C1E0E0400000700000010183B3838 0F070303060C090F313F1C1E0F0F4F4F 1F1F1F07000000007E7F7F0700000000 000000000000FCFE000000E0F8FC3C92 FEFFFEF860C0838712A70200FCFEFCF0 D2F0E0F0F0600000F0F8FCFEF2600000 00000000000000008000000000000000 $FE35 $FE36 $FE37 $FE39 $FE3B $FE3D $FE3E DB FF LSR BCS LSR BCS LSR BCS A $FE66 $E1 $FE47 A $FEA6

$FE40 $FE42 $FE44 $FE47 $FE49 $FE4C $FE4E $FE51 $FE53 $FE55 $FE57 $FE59 $FE5B $FE5E $FE61 $FE63 $FE66 $FE68 $FE69 $FE6B $FE6D $FE6F $FE71 $FE74 $FE77 $FE7A $FE7C $FE7E $FE80 $FE83 $FE86 $FE89 $FE8C $FE8E $FE91 $FE93 $FE96 $FE98 $FE9A $FE9C $FE9E $FEA0 $FEA2 $FEA4 $FEA6 $FEA8 $FEAA $FEAC $FEAD $FEAF $FEB2 $FEB4 $FEB7 $FEB9

LSR $E1 BCS $FE7A JMP $FF6A LDA STA LDA STA STY LDA STA LDX LDY STX STY LDA STA LDA LSR BCC LDA BNE LDA STA JMP #$10 $4000; #$01 $4008; $E3 #$20 $E4 #$5c #$7f $4004; $4005; #$f9 $4007; $E4 A $FE6F #$0d $FE71 #$7c $4006; $FFC6

[NES] Audio - Square 1 [NES] Audio - Triangle

[NES] Audio - Square 2 [NES] Audio - Square 2 [NES] Audio - Square 2

[NES] Audio - Square 2

JMP $FFCA STY LDX LDY STX STX STY STY LDA STA LDA STA LDX STX STX STX LDA STA STA STA DEC BNE LDY INY STY LDA BEQ JSR STA TXA $E3 #$9c #$7f $4000; $4004; $4001; $4005; #$20 $4008; #$01 $400C; #$00 $E9 $EA $EB #$01 $E6 $E7 $E8 $E6 $FEC1 $E9 $E9 $FF1F,Y $FE77 $FFE9 $E6

[NES] [NES] [NES] [NES]

Audio Audio Audio Audio

Square Square Square Square

1 2 1 2

[NES] Audio - Triangle [NES] Audio - Noise control reg

$FEBA $FEBC $FEBE $FEC1 $FEC3 $FEC5 $FEC7 $FEC8 $FECA $FECD $FED0 $FED2 $FED3 $FED5 $FED7 $FEDA $FEDC $FEDE $FEE0 $FEE3 $FEE5 $FEE8 $FEEA $FEEB $FEED $FEF0 $FEF3 $FEF5 $FEF6 $FEF8 $FEFA $FEFD

AND LDX JSR DEC BNE LDY INY STY LDA JSR STA TXA AND LDX JSR DEC BNE LDA STA LDA STA LDY INY STY LDA JSR STA TXA AND LDX JSR JMP

#$3e #$04 $FFD9 $E7 $FEDA $EA $EA $FF33,Y $FFE9 $E7 #$3e #$00 $FFD9 $E8 $FEFD #$09 $400E; #$08 $400F; $EB $EB $FF46,Y $FFE9 $E8 #$3e #$08 $FFD9 $FF6A

[NES] Audio - Noise Frequency reg #1 [NES] Audio - Noise Frequency reg #2

0357000008D408BD08B209AB097C093F 091C08FD08EE09FC09DF060C12180848 CACED413110F9010C4C8070515C4D2D4 8E0C4F00D6D6CA0B19179818CED41513 11D2CACC961857CE0F0F0FCECECECECE 0F0F0FCECECECECE0F0F0FCE $FF5C $FF5E $FF60 $FF62 $FF64 $FF65 $FF67 $FF6A $FF6C $FF6E $FF6F $FF7B LDY LDA LSR BCS LSR BCS JMP $E1 $E3 $E1 $FF7B A $FF9A $FE36

LDA #$00 STA $E1 RTS DB 06 0C 12 47 5F 71 5F 71 8E 71 8E BE STA $E3

$FF7D $FF7F $FF81 $FF83 $FF85 $FF87 $FF89 $FF8C $FF8F $FF92 $FF95 $FF97 $FF9A $FF9C $FF9E $FFA1 $FFA3 $FFA6 $FFA9 $FFAB $FFAE $FFB1 $FFB4 $FFB7 $FFBA $FFBD $FFC0 $FFC2 $FFC4 $FFC6 $FFC8 $FFCA $FFCC $FFCE $FFD0 $FFD3 $FFD6 $FFD9 $FFDA $FFDD $FFDF $FFE2 $FFE5 $FFE8 $FFE9 $FFEA $FFEB $FFEC $FFED $FFEE $FFEF $FFF1 $FFF2 $FFF5

LDA STA LDA STA LDX LDY STX STX STY STY LDA STA LDA LDY CMP BNE LDA STA LDX STX LDA STA STX LDA STA STX LDA BEQ DEC DEC BNE LDA STA LDA STA STA JMP TAY LDA BEQ STA LDA STA RTS TAX ROR TXA ROL ROL ROL AND TAY LDA RTS

#$12 $E4 #$02 $E5 #$9f #$7f $4000; $4004; $4001; $4005; #$20 $4008; $E4 $E5 $FF6F,Y $FFC6 $FF72,Y $4002; #$58 $4003; $FF75,Y $4006; $4007; $FF78,Y $400A; $400B; $E5 $FFC6 $E5 $E4 $FFD6 #$00 $E3 #$10 $4000; $4004; $FF6A

[NES] [NES] [NES] [NES]

Audio Audio Audio Audio

Square Square Square Square

1 2 1 2

[NES] Audio - Triangle

[NES] Audio - Square 1 [NES] Audio - Square 1 [NES] Audio - Square 2 [NES] Audio - Square 2 [NES] Audio - Triangle [NES] Audio - Triangle

[NES] Audio - Square 1 [NES] Audio - Square 2

$FF01,Y $FFE8 $4002,X; $FF00,Y $4003,X;

[NES] Audio - Square 1 [NES] Audio - Square 1

A A A A #$07 $FF1A,Y

$FFF6 EOF

DW $FFFF $01FF NMI Reset IRQ