Family Computer Disk System: Difference between revisions
m (→VRAM Buffer Structure: Plural, since there's the main buffer used as the pipeline, and then several possible buffers that can be transferred by the bios. If I'm understanding right, that is.) |
m (→Load Tileset considerations: Grammar once again) |
||
Line 512: | Line 512: | ||
* Read routines are still obscure and analyzed at the moment | * Read routines are still obscure and analyzed at the moment | ||
=== Load Tileset | === Load Tileset notes === | ||
The ''flags'' parameters | The ''flags'' parameters are as follows: | ||
7 bit 0 | 7 bit 0 | ||
Line 533: | Line 533: | ||
This makes it possible for single bitplane tiles to take all possible color schemes when they end up in VRAM. | This makes it possible for single bitplane tiles to take all possible color schemes when they end up in VRAM. | ||
However, it is not possible to (natively) load single-bitplane graphics directly from the disk to the VRAM; you have to load them to RAM before transferring the data to VRAM. | |||
In read mode all non "data" bitplanes are replaced by dummy reads. | In read mode, all non "data" bitplanes are replaced by dummy reads. | ||
== References == | == References == | ||
* [http://nesdev.parodius.com/FDSListWIN.zip FDS Lister by ccovell] | * [http://nesdev.parodius.com/FDSListWIN.zip FDS Lister by ccovell] |
Revision as of 01:31, 27 February 2012
The Famicom Disk System was a Japan-exclusive storage device for the Famicom, designed to reduce Nintendo's cost of making copies of games by switching from mask ROM chips to a storage medium based on Mitsumi's Quick Disk. Unfortunately for Nintendo, it also reduced the pirates' cost of making copies of games.
.FDS format
fwNES was an NES emulator developed by Fan Wan Yang. Its most lasting contribution to the NES scene was its disk image file format, an image of the Quick Disk media.
The FDS format (file name suffix .fds
) is a way to store Famicom Disk System disk data. It consists of the following sections, in order:
- Header (16 bytes)
- Disk data (65500 * x bytes)
The format of the header is as follows:
- 0-3: Constant $46 $44 $53 $1A ("FDS" followed by MS-DOS end-of-file)
- 4: Number of disk sides in 65500 byte units
- 5-15: Zero filled
Note: Most games are an even number of sides. Ports from NROM were one side. No commercial FDS game had an odd number of sides greater than 1.
Overview
- PRG ROM : 8 KB BIOS at $E000-$FFFF
- PRG RAM: 32 KB at $6000-$DFFF
- CHR capacity: 8 KB RAM
- Nametable mirroring: Controlled by mapper
- Subject to bus conflicts: No
Games are stored on one or multiple disk sides. The FDS BIOS is used to load data from disks to PRG RAM or VRAM, and games can execute from there.
FDS Disk Side format
Each disk side must be structured into block as follows :
1, 2, 3, 4, 3, 4, ...., 3, 4
The 3, 4 pattern should be repeated once per file present on the disk.
From the last file, fill the side with all 0 so that the side has exactly 65550 is reached.
Block format
Disk info block (block 1)
SIZE CONTENTS 1 $01 14 FC Disk String "*NINTENDO-HVC*" (not including the ") 1 Manufacture Code Same code as used in GameBoy(?) 4 Game Name Code 1 Game Version Number (start with $00, increase each revision) 1 Side Number $00: Side-A $01: Side-B 1 Disk Number (fist disk is actually $00) 1 Err.9 (indicate disk # ?, usually $00) 1 Err.10 (indicate disk # ?, usually $00) 1 Boot Read File Code 5 Unknown 3 Manufacture Permit Date Recorded in BCD, in the year of "showa"(+1925) For example, 21 march 2010 becomes $85, $03, $21 (because 2010-1925 = 85) 10 Unknown 3 Created Date Recorded in BCD, in the year of "showa"(+1925) 9 Unknown
The *NINTENDO-HVC*, stored in ASCI standard, strings proves this is a FDS disk, and if the string doesn't exactly match the BIOS will refuse to read the disk further. If the FDS is started with a disk whose side number and disk number aren't both 0, it will be prompted to insert the first disk side. Some games do however make this number 0 even for the second disk to make it bootable as well.
All files which IDs is smaller or equal than the boot read file code will be loaded when the game is booting.
The FDS also has a trademark security system similar to what Sega used on some of its consoles.[1]
The 224-byte text at PPU $2800-$28DF must match data in the BIOS starting at $ED37.
This data says roughly "produced by or under license from Nintendo" in the SMB1/Zelda character encoding.
Traditionally, the first file on a disk is a nametable type file loaded into $2800 which is named KYODAKU-
(きょだく or 許諾 means approval).
File amount block (block 2)
This block contains the total number of files recorded on disk.
SIZE CONTENTS 1 $02 1 File Amount
More files might exist on the disk, but the BIOS load routine will ignore them, those files are called "hidden" files. Some games have a simple copy protection this way : They have their own loading routine similar to the one from the BIOS but hard-code the file amount to a higher number, which will allow for loading hidden files. Early copiers would rely on the block 2 value and forget to copy hidden files, making the copied disk booting but not running the game correctly.
File header block (block 3)
SIZE CONTENTS 1 $03 1 File Number 1 File Indicate Code (file identification code) ID specified at disk-read function call 8 File Name 2 File Address (16-bit little endian) the destination address when loading 2 File Size (16-bit little endian) 1 Kind of File 0:Program (PRAM) 1:Character (CRAM) 2:Name table (VRAM)
The file Number must go in increasing order, first file is 0. File IDs can be freely assigned, and this is the number which will decide which file is loaded from the disk (instead of the file number). An ID smaller than the boot number means the file is a boot file, and will be loaded on first startup.
File names are uppercase ASCII.
File data block (block 4)
SIZE CONTENTS 1 $04 -- disk data
Registers
$402x registers are write-only $403x registers are read-only
IRQ timer low ($4020)
7 bit 0 --------- LLLL LLLL |||| |||| ++++-++++- 8 LSB of IRQ timer
IRQ timer high ($4021)
7 bit 0 --------- LLLL LLLL |||| |||| ++++-++++- 8 MSB of IRQ timer
IRQ timer enable ($4022)
7 bit 0 --------- xxxx xxEx | +-- Enable IRQ timer
Each CPU clock cycle the timer is decremented by one if the enable flag is set. When the counter reach 0, an IRQ is generated. Read $4030 to acknowledge the IRQ.
Note : Since the disk transfer routine also uses IRQs, it's very important to disable timer IRQs before doing any access to the disk.
Master I/O enable ($4023)
7 bit 0 --------- xxxx xxSD || |+- Enable disk I/O registers +-- Enable sound I/O registers
This register sounds obscure. FDS bios just writes $00 then $83 to it.
Write data register ($4024)
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).
FDS Control ($4025)
7 bit 0 --------- IS1B MRTD |||| |||| |||| |||+- Drive Motor Control |||| ||| 0: Stop motor |||| ||| 1: Turn on motor |||| ||+-- Transfer Reset |||| || Set 1 to reset transfer timing to the initial state. |||| |+--- Read / Write mode |||| | (0: write; 1: read) |||| +---- Mirroring (0: horizontal; 1: vertical) |||+------ CRC control (set during CRC calculation of transfer) ||+------- Always set to '1' |+-------- Read/Write Start | Turn on motor. Set to 1 when the drive becomes ready for read/write +--------- Interrupt Transfer 0: Transfer without using IRQ 1: Enable IRQ when the drive becomes ready for
A FDS game that wants to change mirroring probably don't want to touch motor related bits, so it should do a read-modify-write from the pseudo registers (see below).
External connector ($4026)
Output of expansion terminal where there's a shutter on the back of the ram card. The outputs of $4026 (open-collector with 4.7K ohm pull-ups (except on bit 7)), are shared with the inputs on $4033.
Disk Status Register 0 ($4030)
7 bit 0 --------- IExB xxTD |||| || |||| |+- Timer Interrupt (1: an IRQ occurred) |||| +-- 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. |||+------ CRC control (0: CRC passed; 1: CRC error) |+-------- End of Head (1 when disk head is on the most inner track) +--------- Disk Data Read/Write Enable (1 when disk is readable/writable)
Read data register ($4031)
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).
Disk drive status register ($4032)
7 bit 0 --------- xxxx xPRS ||| ||+- Disk flag (0: Disk inserted; 1: Disk not inserted) |+-- Ready flag (0: Disk read; 1: Disk not ready) +--- Protect flag (0: Not write protected; 1: Write protected or disk ejected)
External connector read ($4033)
7 bit 0 --------- BIII IIII |||| |||| |+++-++++- Input from expansion terminal where there's a shutter on the back of the ram card. +--------- Battery status (0: Good; 1: Voltage is low).
When a bit is clear in $4026 port it will read back as '0' here (including battery bit) because of how open collector input works.
Sound ($4040-$4089)
For details on sound information, see FDS audio.
Pseudo-registers
Those registers are used by the FDS bios and FDS programs. They are used to overcome the problem that NES/FDS registers are write only, so it is effectively possible to modify only one bit of them without affecting other bits.
[$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. $F5..$F8 : Used by controller read routines $00..$0F is used as temporary memory for the BIOS. The main program can use it as temporary memory too.
Those values are used by the BIOS to determine action on interrupt.
($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 ($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)
A few important notes :
- After loading the boot files, $102 is set to $35 so that the ($DFFC) vector is used and the BIOS is skipped.
- $103 indicate reset type : $AC = first boot of the game, $53 = the game was soft-reseted by the user
- To use your own IRQ routine, you must manually write $c0 to $101
- There is 3 possible NMI vectors, #3 is used by default.
- On first start, the mirroring is set to horizontal, the stack pointer is $ff, and the I flag is clear. System RAM is filled with values used by the BIOS, and PRG RAM is uninitialized, except for parts of it which has files loaded in.
BIOS calls
Disk acess routines
- Routines takes one or two pointers as arguments. Those are placed directly after the jsr instruction : The subroutines uses the return address in stack to fetch the pointers and fix the return address.
- Memory at $00-$0f will be affected by those routines
- Unlike the vast majority of disk drives, the FDS lacks any kind of intelligent tracking system. All BIOS load and save functions will do access to the whole disk, no matter which data they load/save. A simple way to overcome this problem is to have your own loading routine, similar to the BIOS one but forcing the # of files to a smaller number than it actually is. That way the later files are not accessed at all and the earlier files load faster. Of course the maximal time is still taken when you have to load the files that are late on the disk.
- All non-disk IRQ sources (timer, DMC and APU frame) should be properly disabled before calling any of these routines. The value at [$0101] however, is preserved on entry, and restored on exit.
- On return of those routines, A = $00 means no error occurred, other number is error #. Main program should test if an error occurred with the BEQ or BNE instruction, BEQ will branch if no error, and BNE will branch if there is an error.
- The structures defined below are used to identify files & disks in the access routines. The argument pointers should point to these structures in the program.
Adress | Name | Input parameters | Output parameters | Description |
$e1f8 | LoadFiles | Pointer to Disk ID, Pointer to File List | A = error #, Y = # of files loaded | Loads files specified by DiskID into memory from disk. Load
addresses are decided by the file's header. |
$e237 | AppendFile | Pointer to Disk ID, Pointer to File Header | A = error # | 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). |
$e239 | WriteFile | Pointer to Disk ID, Pointer to File Header, A = file # | A = error # | 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. |
$e2b7 | CheckFileCount | Pointer to Disk ID, A = # to set file count to | A = error # | Reads in disk's file count, compares it to A, then sets the disk's file count to A. |
$e2bb | AdjustFileCount | Pointer to Disk ID, A = number to reduce current file count by | A = error # | Reads in disk's file count, decrements it by A, then writes the new value back. |
$e301 | SetFileCount1 | Pointer to Disk ID, A = file count minus one = # of the last file | A = error # | Set the file count to A + 1 |
$e305 | SetFileCount | Pointer to Disk ID, A = file count | A = error # | Set the file count to A |
$e32a | GetDiskInfo | Pointer to Disk Info | A = error # | Fills DiskInfo up with data read off the current disk. |
Exemple code how to load files
Load jsr LoadFiles .dw DiskID .dw LoadList bne _Error ;Check if there is an error rts _Error jsr PrintError ;If so print the error number and message to screen (include side/disk changing prompts) _sideError lda $4032 and #$01 beq _sideError ;Wait until disk is ejected _insert lda $4032 and #$01 bne _instert ;Wait until disk is inserted jmp Load
DiskID .db $01 ;Manufacturer code .db "NAME" ;4-letter code of game .db $00 ;Version .db $01 ;Disk side .db $00 ;Disk number .db $00, $00 ;Extra disk IDs LoadList ;In this example the files with IDs equal to $02, $03 or $04 will be loaded into memory .db $02, $03, $04, $ff
Error list
Message in bold is the official BIOS message (if there is one) followed by an explanation
- $00: no error
- $01: disk set, ($4032.0) disk not set
- $02: battery, ($4033.7) power supply failure
- $03: ($4032.2) disk is write protected
- $04: Wrong maker ID
- $05: Wrong game
- $06: Wrong game version
- $07: a,b side, wrong side number
- $08: disk no., wrong disk number
- $09: wrong additional disk ID 1
- $0a: wrong additional disk ID 2
- $20: disk trouble, approval check failed
- $21: disk trouble, '*NINTENDO-HVC*' string in block 1 doesn't match
- $22: disk trouble, block type 1 expected
- $23: disk trouble, block type 2 expected
- $24: disk trouble, block type 3 expected
- $25: disk trouble, block type 4 expected
- $27: disk trouble, ($4030.4) block failed CRC
- $28: disk trouble, ($4030.6) file ends prematurely during read
- $29: disk trouble, ($4030.6) file ends prematurely during write
- $30: disk trouble, ($4032.1) disk is full
Disk ID structure
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 list structure
This is a list of 1-byte IDs of files to load. All files that matches any ID in the list are loaded. A list of up to 20 IDs is possible at a time, smaller lists should be terminated by a $ff byte (this implies a file ID can never be $ff).
Multiple files are loaded in the order as they exist on the disk, not in the order of the list.
File header structure
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 file type ($00 : Program; $01 : Character; $02 : Nametable) 0E 2 source address of file data (NOT written to disk) 10 1 source address type ($00 : RAM, $01 : VRAM) 11 -
Disk information structure
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 -
Other BIOS calls
Address | Name | Input parameters | Output parameters | Affected RAM/Registers | Description |
$e149 | Delay132 | 132 clock cycle delay | |||
$e153 | Delayms | X, Y | Delay routine, Y = delay in ms (approximate) | ||
$e161 | DisPFObj | A, $fe | Disable sprites and background | ||
$e16b | EnPFObj | A, $fe | Enable sprites and background | ||
$e170 | DisObj | A, $fe | Disable sprites | ||
$e178 | EnObj | A, $fe | Enable sprites | ||
$e17e | DisPF | A, $fe | Disable background | ||
$e185 | EnPF | A, $fe | Enable background | ||
$e1b2 | VINTWait | $ff | Wait until next Vblank NMI fires, and return (for programs that does it the "everything in main" way). NMI vector selection at $100 is preserved, but further VBlanks are disabled. | ||
$e7bb | VRAMStructWrite | Pointer to VRAM Structure to be written | A, X, Y, $00, $01, $ff | Set VRAM increment to 1 (clear $2000/$ff bit 2), and write a VRAM structure data to VRAM. Read below for information on the structure. | |
$e844 | FetchDirectPtr | $00, $01 = pointer fetched | A, X, Y, $05, $06 | Fetch a direct pointer from the stack (the pointer should be placed after the return address of the routine that calls this one (see "important notes" above)), save the pointer at ($00) and fix the return address. | |
$e86a | WriteVRAMBuffer | A, X, Y, $301, $302 | Write a VRAMBuffer structure to VRAM. Read below for information on the structure. | ||
$e8b3 | ReadVRAMBuffer | X = start address in buffer, Y = # of bytes to read | A, X, Y | Read individual bytes from VRAM to the VRAMBuffer. Apparently this routine has been made so that, when combined to WriteVRAMBuffer, it can easily do read-modify-write operations to individual VRAM bytes. | |
$e8d2 | PrepareVRAMString | A = High VRAM address, X = Low VRAM address, Y = string length, Direct Pointer = data to be written to VRAM | A = $ff : no error, A = $01 : string didn't fit in buffer | A, X, Y, $00, $01, $02, $03, $04, $05, $06 | This routine copies pointed data into the VRAM buffer. |
$e8e1 | PrepareVRAMStrings | A = High VRAM address, X = Low VRAM address, Direct pointer = data to be written to VRAM | A = $ff : no error, A = $01 : data didn't fit in buffer | A, X, Y, $00, $01, $02, $03, $04, $05, $06 | This routine copies a 2D string into the VRAM buffer. The first byte of the data determines the width and height of the following string (in tiles): Upper nybble = height, lower nybble = width. |
$e94f | GetVRAMBufferByte | X = starting index, Y = # of byte to read in buffer (starting at 1), $00, $01 = address to read from | carry clear : a byte was read, carry set : no byte was read | A, X, Y | This routine is somewhat obscure, and was probably meant to be used in pair with ReadVRAMBuffer. First the index is calculated as x+3*(y-1). Then the address stored at that index is compared with the address in ($00). If match, the third byte is read and the routine exit with c clear. If not match, the address is overwritten by the address in ($00) and the routine exit with c set. Exact details in how this could be any useful is under analysis. |
$e97d | Pixel2NamConv | $02 = Pixel X cord, $03 = Pixel Y cord | $00 = High Nametable address, $01 = Low Nametable address | A | This routine convert pixel screen coordinates to corresponding nametable address (assmue no scrolling, and points to first nametable at $2000-$23ff). |
$e997 | Nam2PixelConv | $00 = High nametable address, $01 = low nametable address | $02 = Pixel X cord, $03 = Pixel Y cord | A | This routine convert a nametable address to corresponding pixel coordinates (assume no scrolling). |
$e9b1 | Random | X = Zero Page address where the random bytes are placed, Y = # of shift register bytes (normally $02) | A, X, Y, $00 | This is a shift-register based random number generator, normally takes 2 bytes (using more won't affect random sequence). On reset you are supposed to write some non-zero values here (BIOS uses writes $d0, $d0), and call this routine several times before the data is actually random. Each call of this routine will shift the bytes right. | |
$e9c8 | SpriteDMA | A | This routine does sprite DMA from RAM $200-$2ff | ||
$e9d3 | CounterLogic | A, Y = end zeropage address of counters, X = start zeropage address of counters | A, X, $00 | This decrements several counters in Zeropage. The first counter is a decimal counter 9 -> 8 -> 7 -> ... -> 1 -> 0 -> 9 -> ... Counters 1...A are simply decremented and stays at 0. Counters A+1...Y are decremented when the first counter does a 0 -> 9 transition, and stays at 0. | |
$e9eb | ReadPads | $f5 = Joypad #1 data, $f6 = Joypad #2 data | A, X, $00, $01, | This read hardwired famicom joypads. | |
$ea1a | ReadDownPads | $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data | A, X, $00, $01 | This reads hardwired famicom joypads, and detect up->down button transitions | |
$ea1f | ReadOrDownPads | $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data | A, X, $00, $01 | This read both hardwired famicom and expansion port joypads and detect up->down button transitions. | |
$ea36 | ReadDownVerifyPads | $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data | A, X, $00, $01 | This reads hardwired famicom joypads, and detect up->down button transitions. Data is read until two consecutive read matches to work around the DMC reading glitches. | |
$ea4c | ReadOrDownVerifyPads | $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data | A, X, $00, $01 | This read both hardwired famicom and expansion port joypads and detect up->down button transitions. Data is read until two consecutive read matches to work around the DMC reading glitches. | |
$ea68 | ReadDownExpPads | $f1-$f4 = up->down transitions, $f5-$f8 = Joypda data in the order : Pad1, Pad2, Expansion1, Expansion2 | A, X, $00, $01 | This read both hardwired famicom and expansion port joypad, but stores their data separately instead of ORing them together like the other routines does. This routine is NOT DMC fortified. | |
$ea84 | VRAMFill | A = High VRAM Address (aka tile row #), X = Fill value, Y = # of tile rows OR attribute fill data | A, X, Y, $00, $01, $02 | This routine does 2 things : If A < $20, it fills pattern table data with the value in X for 16 * Y tiles. If A >= $20, it fills the corresponding nametable with the value in X and attribute table with the value in Y. | |
$ead2 | MemFill | A = fill value, X = first page #, Y = last page # | A, X, Y, $00, $01 | This routines fills RAM pages with specified value. | |
$eaea | SetScroll | A | This routine set scroll registers according to values in $fc, $fd and $ff. Should typically be called in VBlank after VRAM updates | ||
$eafd | JumpEngine | A = Jump table entry | A, X, Y, $00, $01 | The instruction calling this is supposed to be followed by a jump table (16-bit pointers little endian, up to 128 pointers). A is the entry # to jump to, return address on stack is used to get jump table entries. | |
$eb13 | ReadKeyboard | Read Family Basic Keyboard expansion (detail is under analysis) | |||
$eb66 | LoadTileset | A = Low VRAM Address & Flags, Y = Hi VRAM Address, X = # of tiles to transfer to/from VRAM | A, X, Y, $00, $01, $02, $03, $04 | This routine can read and write 2BP and 1BP tilesets to/from VRAM. See appendix below about the flags. |
VRAM Buffer Structure
The structure of VRAM buffers are as follows:
SIZE CONTENTS 2 VRAM Address (big endian) 1 bit 0-5 length of data ($0 means a length of 64) bit 6 : 0 = copy, 1 = fill bit 7 : 0 = increment by 1, 1 = increment by 32 n Data to copy to VRAM ..... repeated as many times as needed 1 $ff
- The main structure is terminated by a $ff byte (High address is always supposed to be in $00..$3f range)
- $4c is a "call" command. The 2 bytes that follow is the address of a sub-VRAM structure. The sub-structure can call another sub-structure and so on.
- $60 is a "return" command. It will terminate a sub-structure.
- If Fill mode is used, the routine takes only 1 byte of data which is repeated.
VRAM Buffer notes
The VRAM buffer is located at $300-$3xx. $300 holds the size of the buffer (maximum), and $301 holds the end index of the buffer. The actual buffer lies at $302-$3xx.
- $300 is initialized to the value $7d, effectively making the buffer lie at $300-$37f. It's possible to change the value here to make it bigger or smaller, but the biggest possible value is $fd, making the buffer lie at $300-$3ff.
- Format of the buffer is equivalent to the VRAM structure above, except that there are no sub-structures, no increment by 32 flag and no fill flag.
- For this reason, the VRAM buffer at $302 can be used as a sub-structure.
- Read routines are still obscure and analyzed at the moment
Load Tileset notes
The flags parameters are as follows:
7 bit 0 --------- AAAA MMIT |||| |||| |||| |||+- Fill bit |||| ||+-- Transfer direction (0 = Write tiles, 1 = Read tiles) |||| ++--- Bitplane type (see below) ++++------ Low VRAM Address (aka tile # within a row) 1st bitplane 2nd bitplane Description ----------- ----------- ----------- 0: data data+8 Normal 2-bitplane graphics 1: data fill bit Single bitplane graphics. Fill bit clear : Use colors 0&1 Fill bit set : Use colors 2&3 2: fill bit data Single bitplane graphics. Fill bit clear : Use color 0&2 Fill bit set : Use colors 1&3 3: data^fill bit data Single bitplane graphics. Fill bit clear : Use colors 0&3 Fill bit set : Use colors 1&2
This makes it possible for single bitplane tiles to take all possible color schemes when they end up in VRAM. However, it is not possible to (natively) load single-bitplane graphics directly from the disk to the VRAM; you have to load them to RAM before transferring the data to VRAM. In read mode, all non "data" bitplanes are replaced by dummy reads.