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

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 information 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  >01]        [28<  VCC
(60)  [02]        [27<  A13
(15)  [03]        [26<  A14
(12)  [04]        [25<  /PRG
(13)  [05]        [24]  (11)
(14)  [06]        [23]  (10)
 A7   >07]        [22]  (09)
(58)  [08] LH2833 [21<  R/W
(59)  [09]        [20<  VEE
 --   [10]        [19]  D4
 D5   [11]        [18]  D3
 D6   [12]        [17]  D2
 D7   [13]        [16]  D1
 VEE  >14]        [15]  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 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 Description
0 Electrically connected to pin C on RAM adaptor cable (2C33 pin 48). When active (0), causes disk drive motor to stop. During this time, $4025.1 has no effect.
1 Electrically connected to pin 3 on RAM adaptor cable (2C33 pin 49). When active (0), causes disk drive motor to turn on. This bit must stay active throughout a disk transfer, otherwise $4032.1 will always return 1. When deactivated, disk drive motor stays on until disk head reaches most inner track of disk.
2 Electrically connected to pin 1 on RAM adaptor cable (2C33 pin 50). Controls the disk data transfer direction. Set to 1 to read disk data, 0 to write.
3 Mirroring control. 0 = horizontal; 1 = vertical.
4 CRC control. ROM BIOS subroutines set this bit while processing the CRC data at the end of a block. While it is unclear why this bit is set during block reading, when this bit is set during a block write, this instructs the 2C33 to pipe the current contents of the CRC register to the disk (data in $4024 is effectively ignored during this time).
5 Always set to 1 (use unknown)
6 This bit is typically set while the disk head is in a GAP period on the disk. When this is done, it issues a reset to the 2C33’s internal CRC accumulator.
– During reads, setting this bit instructs the 2C33 to wait for the first set bit (block start mark) to be read off the disk, before accumulating any serial data in the FDS’s internal shift registers, and setting the byte transfer ready flag for the first time (and then every 8-bit subsequent transfer afterwards).
– During writes, setting this bit instructs the 2C33 to immediately load the contents of $4024 into a shift register, set the byte transfer flag, start writing the data from the shift register onto the disk, and repeat this process on subsequent 8-bit transfers. While this bit is 0, data in $4024 is ignored, and a stream of 0’s is written to the disk instead.
7 When set, generates an IRQ when the byte transfer flag raises. When set, generates an IRQ when the byte transfer flag raises.

+-----------+
|$4026/$4033|
+-----------+

External connector output/input, respectfully. The outputs of $4026
(open-collector with 4.7K ohm pull-ups (except on bit 7)), are shared with
the inputs on $4033.

bit	2C33	ext.con
---	----	-------
 0	 44	  3
 1	 43	  4
 2	 42	  5
 3	 41	  6
 4	 40	  7
 5	 39	  8
 6	 38	  9
 7	 37	  -

Bit 7 here is used to report the status of the drive’s power condition (1 =
power good). It is electrically connected to pin 6 on the RAM adaptor cable.
$4026 bit 7 must be set to 1 before the battery status can be checked via
$4033 (otherwise it will always return 0).

+—–+
|$4030|
+—–+
2C33 status.

bit description
— ———–
0: related to the IRQ timer registers (not described here).

1: Byte transfer flag. Set every time 8 bits have been transfered between
the RAM adaptor & disk drive (service $4024/$4031). Reset when $4024, $4031,
or $4030 has been serviced.

4: clear if the CRC register contains 0 (indicating that the transfer passed
the CRC).

6: Unclear operation. Prehaps relates to $4032.1.

7: Unclear operation. Prehaps relates to $4023.0.

+—–+
|$4031|
+—–+
Read data register.

This register is loaded with the contents of an internal shift register
every time the byte transfer flag raises. The shift register recieves it’s
serial data via pin 9 of the RAM adaptor cable (2C33 pin 51).

+—–+
|$4032|
+—–+
Disk drive status.

bit description
— ———–
0: Electrically connected to pin A on RAM adaptor cable (2C33 pin 45). When
active (0), indicates that a disk is inserted in the drive.

1: Electrically connected to pin B on RAM adaptor cable (2C33 pin 46). On
the negative transition of this signal (-_), indicates that the drive head
is currently at the most outer track (beginning of the disk). This bit will
stay 0 until the disk drive head advances to the most inner track (end of
disk), or if $4025.1 is 1 at anytime.

2: Electrically connected to pin 7 on RAM adaptor cable (2C33 pin 47). When
active (0), indicates that a disk is inserted & is writable (as opposed to
being read-only).

6: considered to return 1

ROM BIOS

When the FDS is turned on, what you see & hear (the flashing Nintendo logo,
Mario & Luigi running around, etc.) is being 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):	disk game IRQ vector    (if [$0101] = 11xxxxxxB)
($DFFC):	disk game reset vector  (if ($0102) = $5335, or $AC35)
($DFFA):	disk game NMI vector #3 (if [$0100] = 11xxxxxxB)
($DFF8):	disk game NMI vector #2 (if [$0100] = 10xxxxxxB)
($DFF6):	disk game NMI vector #1 (if [$0100] = 01xxxxxxB)

($0102):	PC action on reset
[$0101]:	PC action on IRQ. set to $80 on reset
[$0100]:	PC action on NMI. set to $C0 on reset

[$FF]:  	value last written to [$2000]   $80 on reset.
[$FE]:  	value last written to [$2001]   $06 on reset
[$FD]:  	value last written to [$2005]#1 0'd on reset.
[$FC]:  	value last written to [$2005]#2 0'd on reset.
[$FB]:  	value last written to [$4016]   0'd on reset.
[$FA]:  	value last written to [$4025]   $2E on reset.
[$F9]:  	value last written to [$4026]   $FF on reset.

The memory in $F9..$FF is always kept in sync with with the aforementioned
ports. This is done because these ports are write-only. Consequentially, the
current value these ports are programmed with can always be read here.

There may be more structured data areas in the zero page (for example, the
BIOS joypad routines use $F5..$F8 for storing controller reads), but only
the listed ones are used by the disk call subroutines.

Booting a disk game

Once a disk’s boot files have been loaded successfully into memory (more on
this later), ($0102) is assigned $AC35, and the BIOS data area (and their
respective ports) are set to the aforementioned reset values. Finally,
interrupts are enabled, and program control is transfered to the ($DFFC)
vector.

ROM BIOS disk procedures information

Pretty much all the disk-related BIOS code resides in the range
[$E1C7..$E7BA], so keep that in mind when looking at a ROM BIOS disassembly.

– The ROM BIOS has disk routines, and disk subroutines. The routines are the
procedures that provide the interface to the software, and the subroutines
provide the routines the interface to the disk hardware. Later in this
document, all known disk routines, and important disk subroutines will be
documented.

+-------------------------+
|disk subroutine data area|
+-------------------------+

All ROM BIOS disk subroutines use a small amount of zero page memory. Disk
routines usually pass parameters to the subroutines via this memory area.
Disk routines do NOT save any data residing in this area prior to calling
subroutines, so procedures that call disk routines must be sure NOT to keep
important data in this area during the call. The following list describes
the zero page memory used by the subroutines, and their common use ([] = 8
bits; () = 16 bits).

($00) first hardcoded parameter
($02) second hardcoded parameter
[$04] previous stack frame
[$05] error retry count
[$06] file counter
[$07] current block type
[$08] boot ID code
[$09] not 0 to make dummy reads
($0A) destination address
($0C) byte xfer count
[$0E] file found counter

Aside from this memory, disk subroutines also expect that the ROM BIOS data
area ($F9..$FF) is maintained properly.

+------------------+
|common disk errors|
+------------------+

When a disk I/O operation fails (for one reason or another), an error # is
generated, reflecting the nature of the failure. The common error #’s used
are as follows (special error numbers will be mentioned later).

00: disk I/O successful (no error)
01: ($4032.0) disk not set
02: ($4033.7) power supply failure (i.e., battery)
03: ($4032.2) disk is write protected
21: ‘*NINTENDO-HVC*’ string in block 1 doesn’t match
22: block type 1 expected
23: block type 2 expected
24: block type 3 expected
25: block type 4 expected
27: ($4030.4) block failed CRC
28: ($4030.6) file ends prematurely during read
29: ($4030.6) file ends prematurely during write
30: ($4032.1) disk head has reached most inner track (end)

ROM BIOS disk subroutine

The following is a documentation on some of the most important ROM BIOS disk
subroutines (including entry point addresses in the NES/FC memory map). This
information is provided mostly to demonstrate the exact procedures the ROM
BIOS follows during disk I/O transfers, since higher-level disk interface
procedures (described later) are a much easier and more practical way of
accessing disk data.

A pseudo-code listing of the low-level events that occur in pretty much all
of the procedures described is provided. The pseudo-code reproduces the I/O
events *exactly* as the ROM BIOS code executes them in (even though some of
the writes seem superfluous). Emulator authors and FDS low-level code
developers should find this information especially useful. In the pseudo
code, “x” is used to represent a bit that doesn’t matter (during
comparisons), or a bit that is not changed (during assignments).

+—–+
|Delay|
+-----+

Entry point: $E153
Y on call: delay in milliseconds
description: a time delay generator.

+--------+
|XferByte|
+--------+

Entry point: $E7A3
A on call: byte to write to disk
A on return: byte read from disk
description: Waits for an IRQ to occur, then reads [$4031] & writes [$4024].
Only the current status of the write flag ($4025.2) determines if data is
actually written to disk, or if valid data is read off the disk.
Logic:

	(Wait for IRQ occurence)
	temp=[$4031]
	[$4024]=A
	A=temp
	return

+—–+
|Error|
+-----+

Entry point: $E781
X on call: error code
A,X on return: error code
Description: restores stack to a prior state before returning, and
terminates data transfer.
Logic:

	S = [$04];		restore stack frame to a previous state
	[$4025] = 0010x11x;	disable scan disk bit
	A = X = ErrorCode
	return

+----------+
|WaitForRdy|
+----------+

Entry Point: $E64D
Description: used to initilalize the disk drive for data transfers.
Logic:

	[$4025] = 0010x110;	stop motor (if it was already running)
	Delay(512)
	[$4025] = 0010x111;	no effect
	[$4025] = 0010x101;	scan disk
	Delay(150);		allow pleanty of time for motor to turn on
	[$4026] = 1xxxxxxx;	enable battery checking
	if ([$4033] = 0xxxxxxx);check battery good bit
	 then Error($02)
	[$4025] = 0010x110;	stop motor again
	[$4025] = 0010x111;	no effect
	[$4025] = 0010x101;	scan disk again
	repeat
	 if ([$4032] = xxxxxxx1)
	  then Error($01);	constantly examine disk set
	until ([$4032] = xxxxxx0x);wait for ready flag to activate
	return

+------------+
|CheckBlkType|
+------------+

Entry point: $E68F
A on call: expected block type
Description: compares the first byte in a new data block to the one passed
in A. Generates an error if test fails.
Logic:

	Delay(5);		advance 5 ms into GAP period
	[$4025] = x1xxxxxx;	wait for block start mark to appear
	[$0101] = 01000000;	set IRQ mode to disk byte transfer
	[$4025] = 1xxxxxxx;	enable disk transfer IRQs
	if (XferByte <> BlockType);test first byte in block
	 then Error($21+BlockType)
	return

+------------+
|WriteBlkType|
+------------+

Entry point: $E6B0
A on call: block type to create
Description: creates a new data block, and writes the passed block type to
it as the first byte.
Logic:

	[$4025] = 00x0x0xx;	set transfer direction to write
	Delay(10);		create a 10 ms GAP period
	[$4024] = 00000000;	zero out write data register
	[$4025] = 01x0x0xx;	start writing data via $4024 to disk
	[$0101] = 01000000;	set IRQ mode to disk byte transfer
	[$4025] = 1xxxxxxx;	enable disk transfer IRQs
	XferByte($80);		write out block start mark
	XferByte(BlockType);	write out specified block type
	return

+------------+
|EndOfBlkRead|
+------------+

Entry point: $E706
Description: called when all (no more and no less) data from a block has
been read in. Tests CRC bit to verify data block’s integrity.
Logic:

	XferByte;		dummy read in CRC byte 1
	if ([$4030] = x1xxxxxx)
	 then Error($28)
	[$4025] = xxx1xxxx;	activate process CRC bit
	XferByte;		dummy read in CRC byte 2
	if ([$4030] = xxx1xxxx);test (CRC accumulator = zero) status
	 then Error($27)
	[$4025] = 00x0x1xx;	disable further disk IRQ's, etc.
	if ([$4032] = xxxxxxx1);check disk set status
	 then Error($01)
	return

+-------------+
|EndOfBlkWrite|
+-------------+

Entry point: $E729
Description: called when all data has been written to a block. Writes out
16-bit CRC value generated by the FDS hardware as last 2 bytes of file.
Logic:

	XferByte;		pipes last byte written into shift registers
	if ([$4030] = x1xxxxxx)
	 then Error($29)
	[$4025] = xxx1xxxx;	activate process CRC bit
	Delay(0.5);		contents of CRC register written for 0.5 ms
	if ([$4032] = xxxxxx1x);check drive ready status
	 then Error($30);	disk head reached end of disk
	[$4025] = 00x0x1xx;	disable further disk IRQ's, etc.
	if ([$4032] = xxxxxxx1);check disk set status
	 then Error($01)
	return

Disk block processing examples
——————————

(reading first block on disk)
	WaitForRdy;		initalize drive & wait for ready flag
	Delay(267);		advance 267 ms into first GAP period
	CheckBlkType();		wait for block start mark & confirm block type
	(where data can be read from disk)
	EndOfBlkRead

(writing first block on disk *)
	WaitForRdy
        [$4025] = 00x0x0xx;	set transfer direction to write
        Delay(398);		create a 398 ms GAP period
	WriteBlkType();		10 more ms of GAP, then write block type
	(where data can be written to disk)
	EndOfBlkWrite

(reading subsequent blocks on disk)
	CheckBlkType()
	(where data can be read from disk)
	EndOfBlkRead

(writing subsequent blocks on disk)
	WriteBlkType()
	(where data can be written to disk)
	EndOfBlkWrite

(ending disk transfer, including if an error occurs)
	Error();		error # is set to 0 when disk xfer successful

*: the ROM BIOS code does not provide any standard way of doing this. Games
that must rewrite the first data block on a FDS disk should follow the
example given here. The delay value listed is an approximation of the size
that first GAP period on the disk should be. The figure is based on the size
that GAP periods on typical FDS disks are (it seems to follow the figure
1.5x, where x is the time the ROM BIOS waits in the gap period during the
reading of the first block).

ROM BIOS disk routines

These are the routines that FDS games use for disk access (presumably the
only ones). They are called directly from FDS game code via JSR $xxxx
instructions. The parameters that the routines work on are hardcoded into
the instruction stream following the JSR $xxxx instruction. Each parameter
is 16-bits, and one or two may be present. The called subroutines always fix
the stack so that program control is returned to the instruction after the
hardcoded parameters.

– when one of these routines is called, the disk set status ($4032.0), and
for routines that write to the disk, write protect status ($4032.2) are
checked before starting the disk transfer. If these tests fail, a final
error # is generated. If an error occurs during a disk transfer, a second
attempt at the transfer is made before the error is considered to be final.

– after any final error is generated, program control is returned to the
instruction to follow the call, and the error code will be in A and X, with
the sign and zero flags set to reflect the error #.

– don’t expect disk calls to return quick; it may take several seconds to
complete.

– the ROM BIOS always uses disk IRQ’s to transfer data between the disk, so
programs must surrender IRQ control to the ROM BIOS during these disk calls.
The value at [$0101] however, is preserved on entry, and restored on exit.

Parameters and procedures
————————-
The types of structures that the disk routines work with will be described
first, and then the disk routines themselves.

+------------------------------------+
|Disk identify header string (DiskID)|
+------------------------------------+

This is a commonly used string. It consists of 10 bytes which are all
compared directly against bytes 15..24 (right after the ‘*NINTENDO-HVC*’
string) of the disk’s header block (block type 1; always the first one on
the disk). If any of the bytes fail the comparison, an appropriate error #
is generated. Comparisons of immaterial data can be skipped by placing an
$FF byte in the appropriate place in the DiskID string (for example, when
the ROM BIOS boots a disk, it sets all the fields in the DiskID string to
-1, except disk side #, and disk #, which are set to 0 (so these fields have
to match 0)). The following chart describes the DiskID structure, and the
error #’s returned when a comparison fails.

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

+-------------------------+
|File load list (LoadList)|
+-------------------------+

This string is used when games need to specify which files to load from disk
into memory. Each byte in LoadList specifies a file to load into memory. As
file headers are sequentially read off the disk, the file identification
code (FileID; byte offset 2 in block types 3) is compared to every element
in the LoadList. If a match is found, the file is loaded. This is done until
there are no more files on the disk (as indicated by the file count stored
in block type 2 on the disk). The LoadList can be terminated by 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	size	description
------	----	-----------
00	1	file ID code
01	8	file name
09	2	load address
0B	2	file data size
0D	1	load area (0 = CPU data; other = PPU)
0E	2	source address of file data (NOT written to disk)
10	1	source address type (0=CPU,other=PPU; NOT written to disk)
11		-

+---------------------------+
|Disk information (DiskInfo)|
+---------------------------+

This is a data structure returned by a subroutine, of collected information
from the disk (list of files on disk, disk size, etc.). The following table
is a description of that structure.

offset	size
------  ----
0	1	game manufacturer code
1	4	game ASCII name string
5	1	game version
6	1	disk side #
7	1	disk #
8	1	extra disk # data
9	1	extra disk # data
A	1	# of files on disk

(the following block will appear for as many files as the "# of files on 
disk" byte indicates)

B	1	file ID code
C	8	file name (ASCII)

(the following is present after the last file info block. Disk size is equal 
to the sum of each file's size entry, plus an extra 261 per file.)

x	1	disk size high byte
x+1	1	disk size low  byte
x+2		-

+----------+
|Load Files|
+----------+

Entry point: $E1F8
RETaddr: pointer to DiskID
RETaddr+2: pointer to LoadList
A on return: error code
Y on return: count of files actually found
Description: Loads files specified by DiskID into memory from disk. Load
addresses are decided by the file’s header.

+-----------+
|Append File|
+-----------+

entry point: $E237
RETaddr: pointer to DiskID
RETaddr+2: pointer to FileHeader
A on return: error code
special error: #$26 if verification stage fails
Description: appends the file data given by DiskID to the disk. This means
that the file is tacked onto the end of the disk, and the disk file count is
incremented. The file is then read back to verify the write. If an error
occurs during verification, the disk’s file count is decremented (logically
hiding the written file).

+----------+
|Write File|
+----------+

Entry point: $E239
RETaddr: pointer to DiskID
RETaddr+2: pointer to FileHeader
A on call: file sequence # for file to write
A on return: error code
special error: #$26 if verification fails
Description: same as “Append File”, but instead of writing the file to the
end of the disk, A specifies the sequential position on the disk to write
the file (0 is the first). This also has the effect of setting the disk’s
file count to the A value, therefore logically hiding any other files that
may reside after the written one.

+--------------------+
|Get Disk Information|
+--------------------+

Entry point: $E32A
RETaddr: pointer to DiskInfo
A on return: error code
Description: fills DiskInfo up with data read off the current disk.

+-----------------+
|Adjust File count|
+-----------------+

Entry point: $E2BB
RETaddr: pointer to DiskID
A on call: number to reduce current file count by
A on return: error code
Special error: #$31 if A is less than the disk’s file count
Description: reads in disk’s file count, decrements it by A, then writes the
new value back.

+----------------+
|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 aforementioned 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
transferred. A single data bit is transferred 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 necessary
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 separate 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	ax,8000h	// this is the block start mark
	sub	si,si		// zero out file byte index ptr

@1:	mov	dl,byte ptr Buf[si]
	inc	si

	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di
	shr dl,1; rcr ax,1; sbb di,di; and di,8408h; xor ax,di

	cmp	si,Size
	jc	@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 #   	*2C33 pin	*RAM pins	signal description
-----   	---------	---------	------------------
1		50		5 (green)	(O) -write
2		64		C (cyan)	(O) VCC (+5VDC)
3		49		6 (blue)	(O) -scan media
4		32		1 (brown)	(O) VEE (ground)
5		52		3 (orange)	(O) write data
6		37		B (pink)	(I) motor on/battery good
7		47		8 (grey)	(I) -writable media
8		-		-       	(I) motor power (note 1)
9		51		4 (yellow)	(I) read  data
A		45		A (black)	(I) -media set
B		46		9 (white)	(I) -ready
C		48		7 (violet)	(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	BCS $E14D
$E151	PLA
$E152	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 objects
EnOBJs:	LDA $FE
$E17A	ORA #$10
$E17C	BNE $E165

;disable playfield
DisPF:	LDA $FE
$E180	AND #$f7
$E182	JMP $E165

;enable playfield
EnPF:	LDA $FE
$E187	ORA #$08
$E189	BNE $E165


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;NMI program 
controlغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;this routine controls what action occurs on a NMI, based on [$0100].

;[$0100]	program control	on NMI
;-------	----------------------
;00xxxxxx:	VINTwait was called; return PC to address that called VINTwait
;01xxxxxx:	use [$DFF6] vector
;10xxxxxx:	use [$DFF8] vector
;11xxxxxx:	use [$DFFA] vector


;NMI branch target
NMI:	BIT $0100
$E18E	BPL $E198
$E190	BVC $E195
$E192	JMP ($DFFA);	11xxxxxx
$E195	JMP ($DFF8);	10xxxxxx
$E198	BVC $E19D
$E19A	JMP ($DFF6);	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 VINT
$E1BC	LDA $FF
$E1BE	ORA #$80
$E1C0	STA $FF
$E1C2	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	LDX $4031
$E1D1	STA $4024
$E1D4	PLA
$E1D5	PLA
$E1D6	PLA
$E1D7	TXA
$E1D8	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	pointer to 10-byte disk header compare string
;RETaddr+2	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:	LDA #$00
$E1FA	STA $0E
$E1FC	LDA #$ff;	get 2 16-bit pointers
$E1FE	JSR GetHCPwNWPchk
$E201	LDA $0101
$E204	PHA
$E205	LDA #$02;	error retry count
$E207	STA $05
$E209	JSR $E21A
$E20C	BEQ $E212;	return address if errors occur
$E20E	DEC $05;	decrease retry count
$E210	BNE $E209
$E212	PLA
$E213	STA $0101
$E216	LDY $0E
$E218	TXA
$E219	RTS

$E21A	JSR ChkDiskHdr
$E21D	JSR Get#ofFiles;returns # in [$06]
$E220	LDA $06
$E222	BEQ $E233;	skip it all if none
$E224	LDA #$03
$E226	JSR CheckBlkType
$E229	JSR FileMatchTest
$E22C	JSR LoadData
$E22F	DEC $06
$E231	BNE $E224
$E233	JSR XferDone
$E236	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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 data to end of disk
$E244	LDA #$03;	2 tries
$E246	STA $05
$E248	DEC $05
$E24A	BEQ $E265
$E24C	JSR WriteLastFile
$E24F	BNE $E248

;verify data at	end of disk
$E251	LDA #$02
$E253	STA $05
$E255	JSR CheckLastFile
$E258	BEQ $E265
$E25A	DEC $05
$E25C	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	PLA
$E266	STA $0101
$E269	TXA
$E26A	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;	use A value
$E2B9	BNE $E2BD
AdjFileCnt:	LDX #$00;	use FileCnt-A
$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;	load file count
$E2D5	SEC
$E2D6	SBC $02;	calculate difference
$E2D8	LDX $09
$E2DA	BEQ $E2DE
$E2DC	LDA $02;	use original accumulator value
$E2DE	LDX #$31;
$E2E0	BCC $E2F1;	branch if A is less than current file count
$E2E2	STA $06

;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	PLA
$E2F2	STA $0101
$E2F5	TXA
$E2F6	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	pointer to destination address for info. to collect


;info. format
;------------
;0	1	manufacturer code
;1	4	game name string
;5	1	game version
;6	1	disk side #
;7	1	disk #1
;8	1	disk #2
;9	1	disk #3
;A	1	# of files on disk

; (the following block will appear for as many files as the files on disk
; byte indicates)

;B	1	file ID code
;C	8	file name (ASCII)

; (the following is present after the last file	info block)

;x	1	disk size high byte
;x+1	1	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 up disk read process
$E346	JSR StartXfer;	verify FDS string at beginning of disk
$E349	LDA $00
$E34B	STA $0A
$E34D	LDA $01
$E34F	STA $0B
$E351	LDY #$00
$E353	STY $02
$E355	STY $03

;load next 10 bytes off disk into RAM at Ptr($0A)
$E357	JSR XferByte
$E35A	STA ($0A),Y
$E35C	INY
$E35D	CPY #$0a
$E35F	BNE $E357
$E361	JSR 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 # of files
$E36C	JSR EndOfBlkRead
$E36F	JSR Get#ofFiles;stores it in [$06]
$E372	LDY #$00
$E374	LDA $06
$E376	STA ($0A),Y;	store # of files in ([$0A])
$E378	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 file name string (8 letters)
$E389	INY
$E38A	JSR XferByte
$E38D	STA ($0A),Y
$E38F	CPY #$09
$E391	BNE $E389

$E393	JSR AddYtoPtr0A;advance 16-bit dest ptr
$E396	JSR XferByte;	throw away low	load address
$E399	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	STA $09
$E3C4	JSR RdData;	dummy read data	off disk
$E3C7	DEC $06;	decrease file count #
$E3C9	BNE $E37A

;store out disk	size
$E3CB	LDA $03
$E3CD	LDY #$01;	fix-up from RdData
$E3CF	STA ($0A),Y
$E3D1	LDA $02
$E3D3	INY
$E3D4	STA ($0A),Y
$E3D6	JSR XferDone
$E3D9	RTS

;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	if disk wasn't set
;	03	if disk is write-protected


;entry points
GetHCPwNWPchk:	SEC;	don't do write-protect check
$E3E8	BCS $E3EB
GetHCPwWPchk:	CLC;	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	LDX #$03;	write-protect error
$E437	LDA $4032
$E43A	AND #$04
$E43C	BEQ $E444

;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:	JSR StartXfer;	check FDS string
$E448	LDX #$04
$E44A	STX $08
$E44C	LDY #$00
$E44E	JSR XferByte
$E451	CMP ($00),Y;	compares code to byte stored at	[Ptr($00)+Y]
$E453	BEQ $E464
$E455	LDX $08
$E457	CPX #$0a
$E459	BNE $E45D
$E45B	LDX #$10
$E45D	LDA ($00),Y
$E45F	CMP #$ff
$E461	JSR XferFailOnNEQ
$E464	INY
$E465	CPY #$01
$E467	BEQ $E46D
$E469	CPY #$05
$E46B	BCC $E46F
$E46D	INC $08
$E46F	CPY #$0a
$E471	BNE $E44E
$E473	JSR XferByte;	boot read file code
$E476	STA $08
$E478	LDY #$1e;	30 iterations
$E47A	JSR XferByte;	dummy read 'til end of block
$E47D	DEY
$E47E	BNE $E47A
$E480	JSR EndOfBlkRead
$E483	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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	TXA;	file ID #
$E4B5	CMP ($02),Y
$E4B7	BEQ $E4CE
$E4B9	INY
$E4BA	CPY #$14
$E4BC	BEQ $E4C4
$E4BE	LDA ($02),Y
$E4C0	CMP #$ff
$E4C2	BNE $E4B4

$E4C4	LDA #$ff
$E4C6	BNE $E4D2
$E4C8	CPX $08;	compare boot read file code to current
$E4CA	BEQ $E4CE
$E4CC	BCS $E4D2;	branch if above	(or equal, but isn't possible)
$E4CE	LDA #$00
$E4D0	INC $0E
$E4D2	STA $09
$E4D4	LDA $0101
$E4D7	BNE $E4D4;	wait until all 8 bytes have been read
$E4D9	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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:	LDA $06
$E4DC	STA $08
$E4DE	BEQ $E4F8;	branch if file count = 0
$E4E0	LDA #$03
$E4E2	JSR CheckBlkType
$E4E5	LDY #$0a;	skip 10 bytes
$E4E7	JSR XferByte
$E4EA	DEY
$E4EB	BNE $E4E7
$E4ED	LDA #$ff
$E4EF	STA $09
$E4F1	JSR LoadData;	dummy read file	data
$E4F4	DEC $08
$E4F6	BNE $E4E0
$E4F8	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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:	LDY #$00
$E4FB	JSR Xfer1stByte
$E4FE	STA $000A,Y
$E501	INY
$E502	CPY #$04
$E504	BNE $E4FB

;Ptr($0A):	destination address
;Ptr($0C):	byte xfer count

RdData:	JSR DecPtr0C
$E509	JSR XferByte;	get kind of file
$E50C	PHA
$E50D	JSR EndOfBlkRead
$E510	LDA #$04
$E512	JSR CheckBlkType
$E515	LDY $09
$E517	PLA
$E518	BNE $E549;	copy to VRAM if	not zero

$E51A	CLC
$E51B	LDA $0A
$E51D	ADC $0C
$E51F	LDA $0B
$E521	ADC $0D
$E523	BCS $E531;	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	JSR XferByte
$E536	CPY #$00
$E538	BNE $E542
$E53A	STA ($0A),Y
$E53C	INC $0A
$E53E	BNE $E542
$E540	INC $0B
$E542	JSR DecPtr0C
$E545	BCS $E533
$E547	BCC $E572

;VRAM data copy
$E549	CPY #$00
$E54B	BNE $E563
$E54D	LDA $FE
$E54F	AND #$e7
$E551	STA $FE
$E553	STA $2001;	[NES] PPU setup	#2
$E556	LDA $2002;	[NES] PPU status
$E559	LDA $0B
$E55B	STA $2006;	[NES] VRAM address select
$E55E	LDA $0A
$E560	STA $2006;	[NES] VRAM address select

$E563	JSR XferByte
$E566	CPY #$00
$E568	BNE $E56D
$E56A	STA $2007;	[NES] VRAM data
$E56D	JSR DecPtr0C
$E570	BCS $E563

$E572	LDA $09
$E574	BNE $E57A
$E576	JSR EndOfBlkRead
$E579	RTS
$E57A	JSR XferByte
$E57D	JSR XferByte
$E580	JMP ChkDiskSet


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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 1	ID
;01 8	Name
;09 2	load address
;0B 2	Size
;0D 1	type (0 = CPU data)
;0E 2	source address of file data (NOT written to disk)
;10 1	source address type (0 = CPU; NOT written to disk)
;11

;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 the file sequence #)
$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 data to disk
$E5D2	LDX #$26;	error #
$E5D4	CMP ($02),Y;	cmp. recorded header byte to what it should be
$E5D6	JSR XferFailOnNEQ
$E5D9	INY;	advance pointer	position
$E5DA	CPY #$0e;	loop is finished if 14 bytes have been checked
$E5DC	BNE $E5C9

;set up next block for reading
$E5DE	LDX $09
$E5E0	BEQ $E616;	branch if writing instead
$E5E2	JSR EndOfBlkRead
$E5E5	JSR LoadSiz&Src;sets up Ptr($0A) & Ptr($0C)
$E5E8	LDA #$04
$E5EA	JSR CheckBlkType

;check source type and read/verify status
$E5ED	LDY #$10
$E5EF	LDA ($02),Y;	check data source type bit
$E5F1	BNE $E624;	branch if NOT in CPU memory map	(PPU instead)
$E5F3	LDY #$00
$E5F5	LDX $09;	check if reading or writing
$E5F7	BEQ $E60A;	branch if writing

;check data on disk
$E5F9	JSR XferByte
$E5FC	LDX #$26
$E5FE	CMP ($0A),Y
$E600	JSR XferFailOnNEQ
$E603	JSR inc0Adec0C
$E606	BCS $E5F9
$E608	BCC $E638

;write data to disk
$E60A	LDA ($0A),Y
$E60C	JSR XferByte
$E60F	JSR inc0Adec0C
$E612	BCS $E60A
$E614	BCC $E638

;set up next block for writing
$E616	JSR EndOfBlkWrite
$E619	JSR LoadSiz&Src;sets up Ptr($0A) & Ptr($0C)
$E61C	LDA #$04
$E61E	JSR WriteBlkType
$E621	JMP $E5ED

;verify data on	disk with VRAM
$E624	LDX $09
$E626	BEQ $E640
$E628	JSR XferByte
$E62B	LDX #$26;	error #
$E62D	CMP $2007
$E630	JSR XferFailOnNEQ
$E633	JSR DecPtr0C
$E636	BCS $E624

;end block reading
$E638	LDX $09
$E63A	BEQ $E649;	branch if writing instead
$E63C	JSR EndOfBlkRead
$E63F	RTS

;write data from VRAM to disk
$E640	LDA $2007;	[NES] VRAM data
$E643	JSR XferByte
$E646	JMP $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	JSR MilSecTimer;0.010 sec delay
$E6BE	LDY #$00
$E6C0	STY $4024;	zero out write register
$E6C3	ORA #$40;	tell FDS to write data to disk NOW
$E6C5	STA $FA
$E6C7	STA $4025
$E6CA	LDA #$80
$E6CC	JSR Xfer1stByte;write out block	start mark
$E6CF	LDA $07
$E6D1	JSR XferByte;	write out block	type
$E6D4	RTS

;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	DEX;	of time to write out entire CRC)
$E741	BNE $E740
$E743	LDX #$30
$E745	LDA $4032
$E748	AND #$02
$E74A	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	LDA ($00),Y
$E7F2	STA $2007;	[NES] VRAM data
$E7F5	DEX
$E7F6	BNE $E7ED

;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;	increment amount
$E82B	BNE $E809;	unconditional

;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 = [Param+1]
$E837	INY
$E838	LDA ($00),Y
$E83A	TAX
$E83B	INY
$E83C	LDA ($00),Y
$E83E	STA $01
$E840	STX $00
$E842	BCS $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	LDA $FF
$E86C	AND #$fb
$E86E	STA $2000;	[NES] PPU setup	#1
$E871	STA $FF
$E873	LDX $2002;	[NES] PPU status
$E876	LDY #$00
$E878	BEQ $E8A5
$E87A	PHA
$E87B	STA $2006;	[NES] VRAM address select
$E87E	INY
$E87F	LDA $0302,Y
$E882	STA $2006;	[NES] VRAM address select
$E885	INY
$E886	LDX $0302,Y
$E889	INY
$E88A	LDA $0302,Y
$E88D	STA $2007;	[NES] VRAM data
$E890	DEX
$E891	BNE $E889
$E893	PLA
$E894	CMP #$3f
$E896	BNE $E8A4
$E898	STA $2006;	[NES] VRAM address select
$E89B	STX $2006;	[NES] VRAM address select
$E89E	STX $2006;	[NES] VRAM address select
$E8A1	STX $2006;	[NES] VRAM address select
$E8A4	INY
$E8A5	LDA $0302,Y
$E8A8	BPL $E87A
$E8AA	STA $0302
$E8AD	LDA #$00
$E8AF	STA $0301
$E8B2	RTS


$E8B3	LDA $2002;	[NES] PPU status
$E8B6	LDA $0300,X
$E8B9	STA $2006;	[NES] VRAM address select
$E8BC	INX
$E8BD	LDA $0300,X
$E8C0	STA $2006;	[NES] VRAM address select
$E8C3	INX
$E8C4	LDA $2007;	[NES] VRAM data
$E8C7	LDA $2007;	[NES] VRAM data
$E8CA	STA $0300,X
$E8CD	INX
$E8CE	DEY
$E8CF	BNE $E8B6
$E8D1	RTS


$E8D2	STA $03
$E8D4	STX $02
$E8D6	STY $04
$E8D8	JSR GetHCparam
$E8DB	LDY #$ff
$E8DD	LDA #$01
$E8DF	BNE $E8F6
$E8E1	STA $03
$E8E3	STX $02
$E8E5	JSR GetHCparam
$E8E8	LDY #$00
$E8EA	LDA ($00),Y
$E8EC	AND #$0f
$E8EE	STA $04
$E8F0	LDA ($00),Y
$E8F2	LSR A
$E8F3	LSR A
$E8F4	LSR A
$E8F5	LSR A
$E8F6	STA $05
$E8F8	LDX $0301
$E8FB	LDA $03
$E8FD	STA $0302,X
$E900	JSR $E93C
$E903	LDA $02
$E905	STA $0302,X
$E908	JSR $E93C
$E90B	LDA $04
$E90D	STA $06
$E90F	STA $0302,X
$E912	JSR $E93C
$E915	INY
$E916	LDA ($00),Y
$E918	STA $0302,X
$E91B	DEC $06
$E91D	BNE $E912
$E91F	JSR $E93C
$E922	STX $0301
$E925	CLC
$E926	LDA #$20
$E928	ADC $02
$E92A	STA $02
$E92C	LDA #$00
$E92E	ADC $03
$E930	STA $03
$E932	DEC $05
$E934	BNE $E8FB
$E936	LDA #$ff
$E938	STA $0302,X
$E93B	RTS


$E93C	INX
$E93D	CPX $0300
$E940	BCC $E94E
$E942	LDX $0301
$E945	LDA #$ff
$E947	STA $0302,X
$E94A	PLA
$E94B	PLA
$E94C	LDA #$01
$E94E	RTS


$E94F	DEX
$E950	DEX
$E951	DEX
$E952	TXA
$E953	CLC
$E954	ADC #$03
$E956	DEY
$E957	BNE $E953
$E959	TAX
$E95A	TAY
$E95B	LDA $0300,X
$E95E	CMP $00
$E960	BNE $E970
$E962	INX
$E963	LDA $0300,X
$E966	CMP $01
$E968	BNE $E970
$E96A	INX
$E96B	LDA $0300,X
$E96E	CLC
$E96F	RTS


$E970	LDA $00
$E972	STA $0300,Y
$E975	INY
$E976	LDA $01
$E978	STA $0300,Y
$E97B	SEC
$E97C	RTS


$E97D	LDA #$08
$E97F	STA $00
$E981	LDA $02
$E983	ASL A
$E984	ROL $00
$E986	ASL A
$E987	ROL $00
$E989	AND #$e0
$E98B	STA $01
$E98D	LDA $03
$E98F	LSR A
$E990	LSR A
$E991	LSR A
$E992	ORA $01
$E994	STA $01
$E996	RTS


$E997	LDA $01
$E999	ASL A
$E99A	ASL A
$E99B	ASL A
$E99C	STA $03
$E99E	LDA $01
$E9A0	STA $02
$E9A2	LDA $00
$E9A4	LSR A
$E9A5	ROR $02
$E9A7	LSR A
$E9A8	ROR $02
$E9AA	LDA #$f8
$E9AC	AND $02
$E9AE	STA $02
$E9B0	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;Random number 
generatorغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;uses a shift register and a XOR to generate pseudo-random numbers.

;algorithm
;---------
;carry	[X]	[X+1]	[X+n]
;-->    --->	--->    --->	--->    --->	--->
;C	765432*0	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 bit	sample with first
$E9B7	LDA $01,X
$E9B9	AND #$02
$E9BB	EOR $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	LDA #$00
$E9CA	STA $2003;	[NES] SPR-RAM address select
$E9CD	LDA #$02
$E9CF	STA $4014;	[NES] Sprite DMA trigger
$E9D2	RTS


$E9D3	STX $00
$E9D5	DEC $00,X
$E9D7	BPL $E9DE
$E9D9	LDA #$09
$E9DB	STA $00,X
$E9DD	TYA
$E9DE	TAX
$E9DF	LDA $00,X
$E9E1	BEQ $E9E5
$E9E3	DEC $00,X
$E9E5	DEX
$E9E6	CPX $00
$E9E8	BNE $E9DF
$E9EA	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;controller read function

;- strobes controllers
;- [$F5] contains 8 reads of bit 0 from [$4016]
;- [$00] contains 8 reads of bit 1 from [$4016]
;- [$F6] contains 8 reads of bit 0 from [$4017]
;- [$01] contains 8 reads of bit 1 from [$4017]

ReadCtrlrs:	LDX $FB
$E9ED	INX
$E9EE	STX $4016;	[NES] Joypad & I/O port for port #1
$E9F1	DEX
$E9F2	STX $4016;	[NES] Joypad & I/O port for port #1
$E9F5	LDX #$08
$E9F7	LDA $4016;	[NES] Joypad & I/O port for port #1
$E9FA	LSR A
$E9FB	ROL $F5
$E9FD	LSR A
$E9FE	ROL $00
$EA00	LDA $4017;	[NES] Joypad & I/O port for port #2
$EA03	LSR A
$EA04	ROL $F6
$EA06	LSR A
$EA07	ROL $01
$EA09	DEX
$EA0A	BNE $E9F7
$EA0C	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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	JSR ORctrlrRead;this instruction is not used
$EA25	LDX #$01
$EA27	LDA $F5,X
$EA29	TAY
$EA2A	EOR $F7,X
$EA2C	AND $F5,X
$EA2E	STA $F5,X
$EA30	STY $F7,X
$EA32	DEX
$EA33	BPL $EA27
$EA35	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ

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


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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;	[NES] VRAM address select
$EA9B	LDY #$00
$EA9D	STY $2006;	[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	STA $2006;	[NES] VRAM address select
$EAC2	LDA #$c0
$EAC4	STA $2006;	[NES] VRAM address select

;  for I:=1 to $40 do [$2007]:=[$02]
$EAC7	LDX #$40
$EAC9	STY $2007;	[NES] VRAM data
$EACC	DEX
$EACD	BNE $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:	PHA
$EAD3	TXA
$EAD4	STY $01
$EAD6	CLC
$EAD7	SBC $01
$EAD9	TAX
$EADA	PLA
$EADB	LDY #$00
$EADD	STY $00
$EADF	STA ($00),Y
$EAE1	DEY
$EAE2	BNE $EADF
$EAE4	DEC $01
$EAE6	INX
$EAE7	BNE $EADF
$EAE9	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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	ASL A
$EAFE	TAY
$EAFF	INY
$EB00	PLA
$EB01	STA $00
$EB03	PLA
$EB04	STA $01
$EB06	LDA ($00),Y
$EB08	TAX
$EB09	INY
$EB0A	LDA ($00),Y
$EB0C	STA $01
$EB0E	STX $00
$EB10	JMP ($0000)


$EB13	LDA $FB
$EB15	AND #$f8
$EB17	STA $FB
$EB19	ORA #$05
$EB1B	STA $4016;	[NES] Joypad & I/O port for port #1
$EB1E	NOP
$EB1F	NOP
$EB20	NOP
$EB21	NOP
$EB22	NOP
$EB23	NOP
$EB24	LDX #$08
$EB26	LDA $FB
$EB28	ORA #$04
$EB2A	STA $4016;	[NES] Joypad & I/O port for port #1
$EB2D	LDY #$0a
$EB2F	DEY
$EB30	BNE $EB2F
$EB32	NOP
$EB33	LDY $FB
$EB35	LDA $4017;	[NES] Joypad & I/O port for port #2
$EB38	LSR A
$EB39	AND #$0f
$EB3B	BEQ $EB62
$EB3D	STA $00,X
$EB3F	LDA $FB
$EB41	ORA #$06
$EB43	STA $4016;	[NES] Joypad & I/O port for port #1
$EB46	LDY #$0a
$EB48	DEY
$EB49	BNE $EB48
$EB4B	NOP
$EB4C	NOP
$EB4D	LDA $4017;	[NES] Joypad & I/O port for port #2
$EB50	ROL A
$EB51	ROL A
$EB52	ROL A
$EB53	AND #$f0
$EB55	ORA $00,X
$EB57	EOR #$ff
$EB59	STA $00,X
$EB5B	DEX
$EB5C	BPL $EB26
$EB5E	LDY $FB
$EB60	ORA #$ff
$EB62	STY $4016;	[NES] Joypad & I/O port for port #1
$EB65	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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:	invert data/fill type

;  1:	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])	 0 if clear; -1	if set
$EBD0	LDA #$00
$EBD2	STA $03
$EBD4	LDA $04
$EBD6	AND #$0f
$EBD8	LSR A
$EBD9	BCC $EBDD
$EBDB	DEC $03

;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;	00xx
$EBE6	DEY
$EBE7	BEQ $EC09;	01xx
$EBE9	DEY
$EBEA	BEQ $EC15;	02xx
$EBEC	DEY;	Y=0

;$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	RTS

$EC22	LDY #$0b
$EC24	LDA ($00),Y
$EC26	STA $02
$EC28	LDA #$02
$EC2A	STA $03
$EC2C	DEY
$EC2D	LDA ($00),Y
$EC2F	LSR A
$EC30	LSR A
$EC31	LSR A
$EC32	LSR A
$EC33	BEQ $EC21
$EC35	STA $04
$EC37	STA $0C
$EC39	LDA ($00),Y
$EC3B	AND #$0f
$EC3D	BEQ $EC21
$EC3F	STA $05
$EC41	LDY #$01
$EC43	LDA ($00),Y
$EC45	TAX
$EC46	DEY
$EC47	LDA ($00),Y
$EC49	BEQ $EC4F
$EC4B	BPL $EC21
$EC4D	LDX #$f4
$EC4F	STX $08
$EC51	LDY #$08
$EC53	LDA ($00),Y
$EC55	LSR A
$EC56	AND #$08
$EC58	BEQ $EC5C
$EC5A	LDA #$80
$EC5C	ROR A
$EC5D	STA $09
$EC5F	INY
$EC60	LDA ($00),Y
$EC62	AND #$23
$EC64	ORA $09
$EC66	STA $09
$EC68	LDY #$03
$EC6A	LDA ($00),Y
$EC6C	STA $0A
$EC6E	LDA $05
$EC70	STA $07
$EC72	LDY #$00
$EC74	STY $0B
$EC76	LDA $04
$EC78	STA $06
$EC7A	LDX $08
$EC7C	TXA
$EC7D	STA ($02),Y
$EC7F	CMP #$f4
$EC81	BEQ $EC87
$EC83	CLC
$EC84	ADC #$08
$EC86	TAX
$EC87	INY
$EC88	INY
$EC89	LDA $09
$EC8B	STA ($02),Y
$EC8D	INY
$EC8E	LDA $0A
$EC90	STA ($02),Y
$EC92	INY
$EC93	INC $0B
$EC95	DEC $06
$EC97	BNE $EC7C
$EC99	LDA $0A
$EC9B	CLC
$EC9C	ADC #$08
$EC9E	STA $0A
$ECA0	DEC $07
$ECA2	BNE $EC76
$ECA4	LDY #$07
$ECA6	LDA ($00),Y
$ECA8	STA $07
$ECAA	DEY
$ECAB	LDA ($00),Y
$ECAD	STA $08
$ECAF	LDA #$00
$ECB1	STA $0A
$ECB3	CLC
$ECB4	LDX $0B
$ECB6	DEY
$ECB7	LDA ($00),Y
$ECB9	CLC
$ECBA	ADC $07
$ECBC	STA $07
$ECBE	LDA #$00
$ECC0	ADC $08
$ECC2	STA $08
$ECC4	DEX
$ECC5	BNE $ECB7
$ECC7	INC $02
$ECC9	LDY #$00
$ECCB	LDA $08
$ECCD	BNE $ECD3
$ECCF	DEC $0A
$ECD1	LDY $07
$ECD3	BIT $09
$ECD5	BMI $ECF5
$ECD7	BVS $ECF7
$ECD9	LDA ($07),Y
$ECDB	BIT $0A
$ECDD	BPL $ECE0
$ECDF	TYA
$ECE0	STA ($02,X)
$ECE2	DEY
$ECE3	BIT $09
$ECE5	BMI $ECE9
$ECE7	INY
$ECE8	INY
$ECE9	LDA #$04
$ECEB	CLC
$ECEC	ADC $02
$ECEE	STA $02
$ECF0	DEC $0B
$ECF2	BNE $ECD9
$ECF4	RTS


$ECF5	BVC $ED09
$ECF7	TYA
$ECF8	CLC
$ECF9	ADC $0B
$ECFB	TAY
$ECFC	DEY
$ECFD	BIT $09
$ECFF	BMI $ECD9
$ED01	LDA #$ff
$ED03	EOR $0C
$ED05	STA $0C
$ED07	INC $0C
$ED09	TYA
$ED0A	CLC
$ED0B	ADC $0C
$ED0D	TAY
$ED0E	LDA $04
$ED10	STA $06
$ED12	DEY
$ED13	BIT $09
$ED15	BMI $ED19
$ED17	INY
$ED18	INY
$ED19	LDA ($07),Y
$ED1B	BIT $0A
$ED1D	BPL $ED20
$ED1F	TYA
$ED20	STA ($02,X)
$ED22	LDA #$04
$ED24	CLC
$ED25	ADC $02
$ED27	STA $02
$ED29	DEC $06
$ED2B	BNE $ED12
$ED2D	TYA
$ED2E	CLC
$ED2F	ADC $0C
$ED31	TAY
$ED32	DEC $05
$ED34	BNE $ED09
$ED36	RTS


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:	ORA #$01
$EE19	STA $4025
$EE1C	AND #$fd
$EE1E	STA $FA
$EE20	STA $4025
$EE23	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;Reset 
vectorغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;disable interrupts (just in case resetting the	CPU doesn't!)
Reset:	SEI

;set up PPU ctrl reg #1
$EE25	LDA #$10
$EE27	STA $2000;	[NES] PPU setup	#1
$EE2A	STA $FF

;clear decimal flag (in case this code is executed on a CPU with dec. mode)
$EE2C	CLD

;set up PPU ctrl reg #2 (disable playfield & objects)
$EE2D	LDA #$06
$EE2F	STA $FE
$EE31	STA $2001;	[NES] PPU setup	#2

;wait at least 1 frame
$EE34	LDX #$02;	loop count = 2 iterations
$EE36	LDA $2002;	[NES] PPU status
$EE39	BPL $EE36;	branch if VBL has not been reached
$EE3B	DEX
$EE3C	BNE $EE36;	exit loop when X = 0

$EE3E	STX $4022;	disable timer interrupt
$EE41	STX $4023;	disable sound &	disk I/O
$EE44	LDA #$83
$EE46	STA $4023;	enable sound & disk I/O
$EE49	STX $FD
$EE4B	STX $FC
$EE4D	STX $FB
$EE4F	STX $4016;	[NES] Joypad & I/O port for port #1
$EE52	LDA #$2e
$EE54	STA $FA
$EE56	STA $4025
$EE59	LDA #$ff
$EE5B	STA $F9
$EE5D	STA $4026
$EE60	STX $4010;	[NES] Audio - DPCM control
$EE63	LDA #$c0
$EE65	STA $4017;	[NES] Joypad & I/O port for port #2
$EE68	LDA #$0f
$EE6A	STA $4015;	[NES] IRQ status / Sound enable
$EE6D	LDA #$80
$EE6F	STA $4080
$EE72	LDA #$e8
$EE74	STA $408A
$EE77	LDX #$ff;	set up stack
$EE79	TXS
$EE7A	LDA #$c0
$EE7C	STA $0100
$EE7F	LDA #$80
$EE81	STA $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 downto $01 do [I]:=$00
$EEA2	LDA #$00
$EEA4	LDX #$f8
$EEA6	STA $00,X
$EEA8	DEX
$EEA9	BNE $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	JSR InitGfx
$EECC	JSR $F0FD
$EECF	LDA #$4a
$EED1	STA $A1
$EED3	LDA #$30
$EED5	STA $B1
$EED7	LDA #$e4
$EED9	STA $83
$EEDB	LDA #$a9
$EEDD	STA $FC

;test if disk inserted
$EEDF	LDA $4032
$EEE2	AND #$01
$EEE4	BEQ $EEEA

$EEE6	LDA #$04
$EEE8	STA $E1
$EEEA	LDA #$34
$EEEC	STA $90
$EEEE	JSR $F376
$EEF1	JSR VINTwait
$EEF4	LDA $90
$EEF6	CMP #$32
$EEF8	BNE $EEFE
$EEFA	LDA #$01
$EEFC	STA $E1
$EEFE	JSR $F0B4
$EF01	JSR RstPPU05
$EF04	JSR EnPfOBJ
$EF07	JSR $EFE8
$EF0A	LDX #$60
$EF0C	LDY #$20
$EF0E	JSR RndmNbrGen
$EF11	JSR $F143
$EF14	JSR $F342
$EF17	LDX #$00
$EF19	JSR $F1E5
$EF1C	LDX #$10
$EF1E	JSR $F1E5
$EF21	LDA #$c0
$EF23	STA $00
$EF25	LDA #$00
$EF27	STA $01
$EF29	JSR $EC22
$EF2C	LDA #$d0
$EF2E	STA $00
$EF30	JSR $EC22
$EF33	LDA $4032
$EF36	AND #$01
$EF38	BNE $EEEA
$EF3A	LDA $FC
$EF3C	BEQ $EF42
$EF3E	LDA #$01
$EF40	STA $FC
$EF42	LDA $90
$EF44	BNE $EEEE
$EF46	JSR DisOBJs
$EF49	JSR VINTwait
$EF4C	JSR PPUdataPrsr,$EFFF
$EF51	JSR PPUdataPrsr,$F01C
$EF56	JSR RstPPU05
$EF59	JSR LoadFiles,$EFF5,$EFF5;load the FDS disk boot files
$EF60	BNE $EF6C
$EF62	JSR $F431
$EF65	BEQ $EFAF
$EF67	JSR $F5FB
$EF6A	LDA #$20
$EF6C	STA $23
$EF6E	JSR InitGfx
$EF71	JSR $F0E1
$EF74	JSR $F0E7
$EF77	JSR $F0ED
$EF7A	JSR $F179
$EF7D	LDA #$10
$EF7F	STA $A3
$EF81	LDA $22
$EF83	BEQ $EF8B
$EF85	LDA #$01
$EF87	STA $83
$EF89	DEC $21
$EF8B	JSR $F376
$EF8E	JSR VINTwait
$EF91	JSR $E86A
$EF94	JSR RstPPU05
$EF97	JSR EnPF
$EF9A	JSR $EFE8
$EF9D	LDA #$02
$EF9F	STA $E1
$EFA1	LDA $A3
$EFA3	BNE $EF8B
$EFA5	LDA $4032
$EFA8	AND #$01
$EFAA	BEQ $EFA5
$EFAC	JMP Reset


$EFAF	LDA #$20
$EFB1	STA $A2
$EFB3	JSR VINTwait
$EFB6	JSR RstPPU05
$EFB9	JSR EnPF
$EFBC	LDX $FC
$EFBE	INX
$EFBF	INX
$EFC0	CPX #$b0
$EFC2	BCS $EFC6
$EFC4	STX $FC
$EFC6	JSR $EFE8
$EFC9	LDA $A2
$EFCB	BNE $EFB3
$EFCD	LDA #$35
$EFCF	STA $0102
$EFD2	LDA #$ac
$EFD4	STA $0103
$EFD7	JSR DisPF
$EFDA	LDY #$07
$EFDC	JSR $F48C
$EFDF	LDA #$00
$EFE1	STA $FD
$EFE3	STA $FC
$EFE5	JMP $EE9B


$EFE8	JSR $FF5C
$EFEB	LDX #$80
$EFED	LDA #$9f
$EFEF	LDY #$bf
$EFF1	JSR $E9D3
$EFF4	RTS


FFFFFFFFFFFF0000FFFF

$EFFF	21A6 54 24
	FF

$F004	21A6 14 19150E0A1C0E241C0E1D240D121C14240C0A1B0D
	FF

$F01C	21A6 0E 1718202415180A0D121710262626
	FF

0D121C14241C0E1D0B0A1D1D0E1B2224
0A250B241C120D0E0D121C1424171826
21A6140D121C14241D1B181E0B150E24
240E1B1B260200FF20E810191B0A1624
0C1B0A16242424242418142168041918
1B1D3F00080F200F0F0F0F0F0F2BC050
002BD07055FF

;$F094
80B80000000000001000320000000100
80B800F000000000000132180000FF00


$F0B4	LDA $FC
$F0B6	BEQ $F0C0
$F0B8	DEC $FC
$F0BA	BNE $F0C0
$F0BC	LDA #$10
$F0BE	STA $94
$F0C0	LDX $94
$F0C2	BEQ $F0CD
$F0C4	DEX
$F0C5	BEQ $F0E1
$F0C7	DEX
$F0C8	BEQ $F0E7
$F0CA	DEX
$F0CB	BEQ $F0ED
$F0CD	JSR $E9C8
$F0D0	JSR $E86A
$F0D3	LDA $92
$F0D5	BNE $F0F3
$F0D7	JSR PPUdataPrsr,$EFFF
$F0DC	LDA #$40
$F0DE	STA $92
$F0E0	RTS


$F0E1	JSR PPUdataPrsr,$F716
$F0E6	RTS


$F0E7	JSR PPUdataPrsr,$F723
$F0EC	RTS


$F0ED	JSR PPUdataPrsr,$F72C
$F0F2	RTS


$F0F3	CMP #$2e
$F0F5	BNE $F0FC
$F0F7	JSR PPUdataPrsr,$F004
$F0FC	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	LDA $84
$F145	BNE $F156
$F147	LDA #$04
$F149	STA $84
$F14B	LDX #$d0
$F14D	DEC $022C,X
$F150	DEX
$F151	DEX
$F152	DEX
$F153	DEX
$F154	BNE $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	DB  01 02 07 08


$F179	LDY #$18
$F17B	LDA $F04D,Y
$F17E	STA $003F,Y
$F181	DEY
$F182	BNE $F17B
$F184	LDA $23
$F186	AND #$0f
$F188	STA $56
$F18A	LDA $23
$F18C	LSR A
$F18D	LSR A
$F18E	LSR A
$F18F	LSR A
$F190	STA $55
$F192	CMP #$02
$F194	BEQ $F1BD
$F196	LDY #$0e
$F198	LDA #$24
$F19A	STA $0042,Y
$F19D	DEY
$F19E	BNE $F19A
$F1A0	LDY #$05
$F1A2	LDA $23
$F1A4	DEY
$F1A5	BEQ $F1BD
$F1A7	CMP $F174,Y
$F1AA	BNE $F1A4
$F1AC	TYA
$F1AD	ASL A
$F1AE	ASL A
$F1AF	ASL A
$F1B0	TAX
$F1B1	LDY #$07
$F1B3	DEX
$F1B4	LDA $F02E,X
$F1B7	STA $0043,Y
$F1BA	DEY
$F1BB	BPL $F1B3
$F1BD	JSR PPUdataPrsr,$0040
$F1C2	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;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	JSR $F1F2
$F1E8	JSR $F2EC
$F1EB	JSR $F273
$F1EE	JSR $F2C6
$F1F1	RTS


$F1F2	LDA $20,X
$F1F4	BNE $F227
$F1F6	LDA $C0,X
$F1F8	BNE $F227
$F1FA	LDA $B0
$F1FC	BNE $F227
$F1FE	LDA $81,X
$F200	BNE $F227
$F202	LDA $62,X
$F204	AND #$3c
$F206	STA $81,X
$F208	TXA
$F209	BNE $F228
$F20B	LDA $B2
$F20D	BNE $F236
$F20F	LDA $D0
$F211	BEQ $F23D
$F213	LDA $22
$F215	BNE $F21D
$F217	LDA $C3
$F219	CMP #$78
$F21B	BCC $F24D
$F21D	LDA #$00
$F21F	STA $C8,X
$F221	STA $CF,X
$F223	LDA #$ff
$F225	STA $CE,X
$F227	RTS


$F228	LDA $C0
$F22A	BEQ $F25A
$F22C	LDA $22
$F22E	BNE $F21D
$F230	LDA $63,X
$F232	CMP #$80
$F234	BCS $F24D
$F236	LDA #$00
$F238	STA $CF,X
$F23A	STA $CE,X
$F23C	RTS


$F23D	LDA $C8
$F23F	BNE $F247
$F241	LDA $63,X
$F243	CMP #$c0
$F245	BCC $F21D
$F247	LDA $64,X
$F249	CMP #$80
$F24B	BCC $F236
$F24D	LDA #$10
$F24F	STA $C8,X
$F251	LDA #$00
$F253	STA $CF,X
$F255	LDA #$01
$F257	STA $CE,X
$F259	RTS


$F25A	LDA $64,X
$F25C	LDY $C8
$F25E	BEQ $F264
$F260	CMP #$40
$F262	BCC $F24D
$F264	CMP #$c0
$F266	BCC $F236
$F268	LDA #$40
$F26A	STA $CF,X
$F26C	LDA #$00
$F26E	STA $CE,X
$F270	STA $C8,X
$F272	RTS


$F273	LDA $20,X
$F275	BEQ $F2AA
$F277	BMI $F2AB
$F279	CLC
$F27A	LDA #$30
$F27C	ADC $CD,X
$F27E	STA $CD,X
$F280	LDA #$00
$F282	ADC $CC,X
$F284	STA $CC,X
$F286	CLC
$F287	LDA $CD,X
$F289	ADC $C2,X
$F28B	STA $C2,X
$F28D	LDA $CC,X
$F28F	ADC $C1,X
$F291	CMP #$b8
$F293	BCC $F2A4
$F295	TXA
$F296	BNE $F2B6
$F298	LDA $60,X
$F29A	AND #$30
$F29C	STA $81,X
$F29E	LDA #$00
$F2A0	STA $20,X
$F2A2	LDA #$b8
$F2A4	STA $C1,X
$F2A6	LDA #$03
$F2A8	STA $C5,X
$F2AA	RTS


$F2AB	DEC $20,X
$F2AD	LDA #$fd
$F2AF	STA $CC,X
$F2B1	LDA #$00
$F2B3	STA $CD,X
$F2B5	RTS


$F2B6	STA $C8,X
$F2B8	LDA #$01
$F2BA	STA $CE,X
$F2BC	LDA #$c0
$F2BE	STA $CF,X
$F2C0	LDA #$ff
$F2C2	STA $81,X
$F2C4	BNE $F29E
$F2C6	LDA $B0
$F2C8	BNE $F2E7
$F2CA	LDA $A1,X
$F2CC	BNE $F2E7
$F2CE	LDA $C0,X
$F2D0	BEQ $F2E7
$F2D2	LDA $62,X
$F2D4	ORA #$10
$F2D6	AND #$3c
$F2D8	STA $81,X
$F2DA	LDY #$10
$F2DC	LDA $F094,X
$F2DF	STA $C0,X
$F2E1	INX
$F2E2	DEY
$F2E3	BNE $F2DC
$F2E5	STY $B0,X
$F2E7	RTS


$F2E8	DB  00 02 01 02


$F2EC	LDA $C0,X
$F2EE	BNE $F329
$F2F0	CLC
$F2F1	LDA $CF,X
$F2F3	ADC $C4,X
$F2F5	STA $C4,X
$F2F7	LDA $CE,X
$F2F9	ADC $C3,X
$F2FB	LDY $B0
$F2FD	CPY #$20
$F2FF	BCS $F315
$F301	CMP #$f8
$F303	BCC $F32A
$F305	CPY #$1f
$F307	BCS $F315
$F309	LDA $60,X
$F30B	AND #$2f
$F30D	ORA #$06
$F30F	STA $A1,X
$F311	LDA #$80
$F313	STA $C0,X
$F315	STA $C3,X
$F317	LSR A
$F318	LSR A
$F319	AND #$03
$F31B	TAY
$F31C	LDA $CE,X
$F31E	ORA $CF,X
$F320	BNE $F324
$F322	LDY #$01
$F324	LDA $F2E8,Y
$F327	STA $C5,X
$F329	RTS


$F32A	CMP #$78
$F32C	BNE $F315
$F32E	CPX $22
$F330	BNE $F315
$F332	LDY $20,X
$F334	BNE $F315
$F336	LDY #$00
$F338	STY $CE,X
$F33A	STY $CF,X
$F33C	LDY #$80
$F33E	STY $20,X
$F340	BNE $F315
$F342	LDA $B0
$F344	BNE $F36D
$F346	LDA $C0
$F348	ORA $D0
$F34A	BNE $F36D
$F34C	CLC
$F34D	LDA $C3
$F34F	ADC #$19
$F351	CMP $D3
$F353	BCC $F36D
$F355	STA $D3
$F357	LDA #$02
$F359	STA $CE
$F35B	STA $DE
$F35D	LDA #$00
$F35F	STA $CF
$F361	STA $DF
$F363	LDA #$10
$F365	STA $C8
$F367	STA $D8
$F369	LDA #$30
$F36B	STA $B0
$F36D	RTS


$F36E	DB  2A 0A 25 05	21 01 27 16


$F376	LDY #$08
$F378	LDA $83
$F37A	BNE $F3C8
$F37C	LDA $93
$F37E	BNE $F3EF
$F380	LDX #$00
$F382	LDA $C1,X
$F384	CMP #$a4
$F386	BCS $F39E
$F388	LDA #$20
$F38A	LDY $B2
$F38C	BNE $F39C
$F38E	LDA #$08
$F390	LDY $65
$F392	CPY #$18
$F394	BCS $F39C
$F396	LDA #$08
$F398	STA $B2
$F39A	LDA #$20
$F39C	STA $83,X
$F39E	CPX #$10
$F3A0	LDX #$10
$F3A2	BCC $F382
$F3A4	LDA $22
$F3A6	BEQ $F3C7
$F3A8	LDA $82
$F3AA	BNE $F3C7
$F3AC	LDA #$08
$F3AE	STA $82
$F3B0	LDX #$0f
$F3B2	LDA $47
$F3B4	CMP #$0f
$F3B6	BNE $F3BA
$F3B8	LDX #$16
$F3BA	STX $47
$F3BC	LDA #$3f
$F3BE	LDX #$08
$F3C0	LDY #$08
$F3C2	JSR $E8D2,$0040
$F3C7	RTS


$F3C8	LDA $F634,Y
$F3CB	STA $003F,Y
$F3CE	DEY
$F3CF	BNE $F3C8
$F3D1	INC $21
$F3D3	LDA $21
$F3D5	AND #$06
$F3D7	TAY
$F3D8	LDA $F36E,Y
$F3DB	STA $42
$F3DD	LDA $F36F,Y
$F3E0	STA $43
$F3E2	LDY #$00
$F3E4	LDA $B2
$F3E6	BNE $F3EA
$F3E8	LDY #$10
$F3EA	STY $22
$F3EC	JMP $F3BC


$F3EF	LDA $F63F,Y
$F3F2	STA $003F,Y
$F3F5	DEY
$F3F6	BNE $F3EF
$F3F8	BEQ $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	JSR VINTwait
$F42B	JSR PPUdataPrsr,InitNT;	initialize name	table
$F430	RTS


;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ
;غغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغغ

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


$F484	DB  02 30 10 29	32 00 29 10


$F48C	LDX #$03
$F48E	LDA $F484,Y
$F491	STA $07,X
$F493	DEY
$F494	DEX
$F495	BPL $F48E
$F497	LDA #$29
$F499	STA $0B
$F49B	LDA $07
$F49D	LDX #$01
$F49F	LDY $09
$F4A1	JSR CPUtoPPUcpy,$0010
$F4A6	LDA $08
$F4A8	LDX #$01
$F4AA	LDY $0A
$F4AC	JSR CPUtoPPUcpy,$0010
$F4B1	LDY #$01
$F4B3	CLC
$F4B4	LDA #$10
$F4B6	ADC $0007,Y
$F4B9	STA $0007,Y
$F4BC	LDA #$00
$F4BE	ADC $0009,Y
$F4C1	STA $0009,Y
$F4C4	DEY
$F4C5	BPL $F4B3
$F4C7	DEC $0B
$F4C9	BNE $F49B
$F4CB	RTS


$F4CC	LDA #$20
$F4CE	LDX #$24
$F4D0	LDY #$00
$F4D2	JSR VRAMfill
$F4D5	JSR VINTwait
$F4D8	JSR PPUdataPrsr,$F066
$F4DD	JSR $F5FB
$F4E0	BNE $F527
$F4E2	LDA #$00
$F4E4	LDX #$00
$F4E6	LDY #$00
$F4E8	JSR CPUtoPPUcpy,$C000
$F4ED	LDA #$00
$F4EF	LDX #$00
$F4F1	LDY #$10
$F4F3	JSR CPUtoPPUcpy,$D000
$F4F8	LDA #$02
$F4FA	LDX #$00
$F4FC	LDY #$00
$F4FE	JSR CPUtoPPUcpy,$C000
$F503	LDA #$02
$F505	LDX #$00
$F507	LDY #$10
$F509	JSR CPUtoPPUcpy,$D000
$F50E	LDA #$C0
$F510	STA $01
$F512	LDY #$00
$F514	STY $00
$F516	LDX #$20
$F518	LDA #$7f
$F51A	ADC #$02
$F51C	JSR $F61B
$F51F	BEQ $F54E
$F521	LDA $01
$F523	AND #$03
$F525	STA $01
$F527	LDA #$11
$F529	STA $0B
$F52B	LDY #$03
$F52D	LDA $00
$F52F	TAX
$F530	AND #$0F
$F532	STA $0007,Y
$F535	DEY
$F536	TXA
$F537	LSR A
$F538	LSR A
$F539	LSR A
$F53A	LSR A
$F53B	STA $0007,Y
$F53E	LDA $01
$F540	DEY
$F541	BPL $F52F
$F543	LDA #$20
$F545	LDX #$f4
$F547	LDY #$05
$F549	JSR $E8D2,$0007
$F54E	JSR LoadFonts
$F551	JSR GetCtrlrSts
$F554	LDA $F7
$F556	CMP #$81
$F558	BNE $F5B8
$F55A	JSR PPUdataPrsr,$F56B
$F55F	JSR VINTwait
$F562	JSR RstPPU05
$F565	JSR EnPF
$F568	JMP $F568


	20E7 11 020C03032412171D0E1B170A15241B1816
	2163 19 191B18101B0A160E0D240B22241D0A140A18241C0A200A1718
	21A3 19 1712171D0E170D18240C1827151D0D26240D0E1F2617182602
	FF


$F5B8	LDA #$01
$F5BA	STA $0F
$F5BC	LDA #$ff
$F5BE	CLC
$F5BF	PHA
$F5C0	PHP
$F5C1	JSR VINTwait
$F5C4	JSR $E86A
$F5C7	JSR RstPPU05
$F5CA	JSR EnPF
$F5CD	DEC $0F
$F5CF	BNE $F5DD
$F5D1	PLP
$F5D2	PLA
$F5D3	STA $4026
$F5D6	ROL A
$F5D7	PHA
$F5D8	PHP
$F5D9	LDA #$19
$F5DB	STA $0F
$F5DD	LDA $4033
$F5E0	LDX #$07
$F5E2	LDY #$01
$F5E4	ASL A
$F5E5	BCS $F5E8
$F5E7	DEY
$F5E8	STY $07,X
$F5EA	DEX
$F5EB	BPL $F5E2
$F5ED	LDA #$21
$F5EF	LDX #$70
$F5F1	LDY #$08
$F5F3	JSR $E8D2,$0007
$F5F8	JMP $F5C1


$F5FB	LDA #$60
$F5FD	LDX #$80
$F5FF	STX $03
$F601	PHA
$F602	STA $01
$F604	LDY #$00
$F606	STY $00
$F608	CLV
$F609	JSR $F61B
$F60C	PLA
$F60D	STA $01
$F60F	STY $00
$F611	LDX $03
$F613	LDA #$7f
$F615	ADC #$02
$F617	JSR $F61B
$F61A	RTS


$F61B	STX $02
$F61D	LDA $02
$F61F	BVS $F62E
$F621	STA ($00),Y
$F623	INC $02
$F625	DEY
$F626	BNE $F61D
$F628	INC $01
$F62A	DEX
$F62B	BNE $F61B
$F62D	RTS


$F62E	CMP ($00),Y
$F630	BEQ $F623
$F632	STY $00
$F634	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	DB  FF

$FE36	LSR A
$FE37	BCS $FE66
$FE39	LSR $E1
$FE3B	BCS $FE47
$FE3D	LSR A
$FE3E	BCS $FEA6
$FE40	LSR $E1
$FE42	BCS $FE7A
$FE44	JMP $FF6A


$FE47	LDA #$10
$FE49	STA $4000;	[NES] Audio - Square 1
$FE4C	LDA #$01
$FE4E	STA $4008;	[NES] Audio - Triangle
$FE51	STY $E3
$FE53	LDA #$20
$FE55	STA $E4
$FE57	LDX #$5c
$FE59	LDY #$7f
$FE5B	STX $4004;	[NES] Audio - Square 2
$FE5E	STY $4005;	[NES] Audio - Square 2
$FE61	LDA #$f9
$FE63	STA $4007;	[NES] Audio - Square 2
$FE66	LDA $E4
$FE68	LSR A
$FE69	BCC $FE6F
$FE6B	LDA #$0d
$FE6D	BNE $FE71
$FE6F	LDA #$7c
$FE71	STA $4006;	[NES] Audio - Square 2
$FE74	JMP $FFC6


$FE77	JMP $FFCA


$FE7A	STY $E3
$FE7C	LDX #$9c
$FE7E	LDY #$7f
$FE80	STX $4000;	[NES] Audio - Square 1
$FE83	STX $4004;	[NES] Audio - Square 2
$FE86	STY $4001;	[NES] Audio - Square 1
$FE89	STY $4005;	[NES] Audio - Square 2
$FE8C	LDA #$20
$FE8E	STA $4008;	[NES] Audio - Triangle
$FE91	LDA #$01
$FE93	STA $400C;	[NES] Audio - Noise control reg
$FE96	LDX #$00
$FE98	STX $E9
$FE9A	STX $EA
$FE9C	STX $EB
$FE9E	LDA #$01
$FEA0	STA $E6
$FEA2	STA $E7
$FEA4	STA $E8
$FEA6	DEC $E6
$FEA8	BNE $FEC1
$FEAA	LDY $E9
$FEAC	INY
$FEAD	STY $E9
$FEAF	LDA $FF1F,Y
$FEB2	BEQ $FE77
$FEB4	JSR $FFE9
$FEB7	STA $E6
$FEB9	TXA
$FEBA	AND #$3e
$FEBC	LDX #$04
$FEBE	JSR $FFD9
$FEC1	DEC $E7
$FEC3	BNE $FEDA
$FEC5	LDY $EA
$FEC7	INY
$FEC8	STY $EA
$FECA	LDA $FF33,Y
$FECD	JSR $FFE9
$FED0	STA $E7
$FED2	TXA
$FED3	AND #$3e
$FED5	LDX #$00
$FED7	JSR $FFD9
$FEDA	DEC $E8
$FEDC	BNE $FEFD
$FEDE	LDA #$09
$FEE0	STA $400E;	[NES] Audio - Noise Frequency reg #1
$FEE3	LDA #$08
$FEE5	STA $400F;	[NES] Audio - Noise Frequency reg #2
$FEE8	LDY $EB
$FEEA	INY
$FEEB	STY $EB
$FEED	LDA $FF46,Y
$FEF0	JSR $FFE9
$FEF3	STA $E8
$FEF5	TXA
$FEF6	AND #$3e
$FEF8	LDX #$08
$FEFA	JSR $FFD9
$FEFD	JMP $FF6A


0357000008D408BD08B209AB097C093F
091C08FD08EE09FC09DF060C12180848
CACED413110F9010C4C8070515C4D2D4
8E0C4F00D6D6CA0B19179818CED41513
11D2CACC961857CE0F0F0FCECECECECE
0F0F0FCECECECECE0F0F0FCE


$FF5C	LDY $E1
$FF5E	LDA $E3
$FF60	LSR $E1
$FF62	BCS $FF7B
$FF64	LSR A
$FF65	BCS $FF9A
$FF67	JMP $FE36


$FF6A	LDA #$00
$FF6C	STA $E1
$FF6E	RTS


$FF6F	DB  06 0C 12 47	5F 71 5F 71 8E 71 8E BE


$FF7B	STA $E3
$FF7D	LDA #$12
$FF7F	STA $E4
$FF81	LDA #$02
$FF83	STA $E5
$FF85	LDX #$9f
$FF87	LDY #$7f
$FF89	STX $4000;	[NES] Audio - Square 1
$FF8C	STX $4004;	[NES] Audio - Square 2
$FF8F	STY $4001;	[NES] Audio - Square 1
$FF92	STY $4005;	[NES] Audio - Square 2
$FF95	LDA #$20
$FF97	STA $4008;	[NES] Audio - Triangle
$FF9A	LDA $E4
$FF9C	LDY $E5
$FF9E	CMP $FF6F,Y
$FFA1	BNE $FFC6
$FFA3	LDA $FF72,Y
$FFA6	STA $4002;	[NES] Audio - Square 1
$FFA9	LDX #$58
$FFAB	STX $4003;	[NES] Audio - Square 1
$FFAE	LDA $FF75,Y
$FFB1	STA $4006;	[NES] Audio - Square 2
$FFB4	STX $4007;	[NES] Audio - Square 2
$FFB7	LDA $FF78,Y
$FFBA	STA $400A;	[NES] Audio - Triangle
$FFBD	STX $400B;	[NES] Audio - Triangle
$FFC0	LDA $E5
$FFC2	BEQ $FFC6
$FFC4	DEC $E5
$FFC6	DEC $E4
$FFC8	BNE $FFD6
$FFCA	LDA #$00
$FFCC	STA $E3
$FFCE	LDA #$10
$FFD0	STA $4000;	[NES] Audio - Square 1
$FFD3	STA $4004;	[NES] Audio - Square 2
$FFD6	JMP $FF6A


$FFD9	TAY
$FFDA	LDA $FF01,Y
$FFDD	BEQ $FFE8
$FFDF	STA $4002,X;	[NES] Audio - Square 1
$FFE2	LDA $FF00,Y
$FFE5	STA $4003,X;	[NES] Audio - Square 1
$FFE8	RTS


$FFE9	TAX
$FFEA	ROR A
$FFEB	TXA
$FFEC	ROL A
$FFED	ROL A
$FFEE	ROL A
$FFEF	AND #$07
$FFF1	TAY
$FFF2	LDA $FF1A,Y
$FFF5	RTS


$FFF6	DW  $FFFF $01FF	NMI Reset IRQ


EOF

Leave a Comment