├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── apireport.py ├── counterlogic.asm ├── delay.asm ├── fetchdirectptr.asm ├── font.asm ├── freedisksys.asm ├── gamepads.asm ├── gethardcodedpointers.asm ├── getvrambufferbyte.asm ├── irq.asm ├── loadtileset.asm ├── nametable.asm ├── nmi.asm ├── ppumask.asm ├── random.asm ├── reset.asm ├── vrambuffers.asm ├── vramfill.asm ├── vramstrings.asm └── vramstructwrite.asm /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.asm text 7 | *.h text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | 11 | # Denote all files that are truly binary and should not be modified. 12 | *.png binary 13 | *.jpg binary 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.bin 3 | *.lst 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FreeDiskSysROM 2 | 3 | This project has one goal - a compatible re-implementation of the Famicom Disk System BIOS under an OSS license. Unlike the Famicom console itself, which has no program ROM built-in, the Famicom Disk System includes an 8 KiB PRG-ROM containing disk I/O routines, VRAM transfer routines, joypad reading code, an animation featuring Mario and Luigi when no disk is present in the drive, and more. The code, data, graphics, music, and animation contained in the original BIOS are copyrighted by Nintendo. 4 | 5 | FreeDiskSysROM aims to provide a replacement for the original FDS BIOS that can be freely redistributed and that is capable of running all published FDS software. 6 | 7 | # Audience 8 | 9 | ## Emulators 10 | 11 | Famicom and NES emulators historically require a dump of the Famicom Disk System BIOS to be able to emulate FDS titles. Emulators can ship FreeDiskSysROM with their installers instead of requiring end-users to either copy the ROM out of their own FDS hardware or breaking copyright law by downloading a BIOS rip from elsewhere on the Internet. 12 | 13 | ## Clone hardware 14 | 15 | Modern hardware clones of the FDS RAM Adapter or FPGA re-implementations of the entire Famicom Disk System also need a BIOS. 16 | 17 | # Status 18 | 19 | ## APIs 20 | 21 | | Address | Name | # of Games | Implemented | 22 | | ------- | ---- | ------- | ----------- | 23 | | $e149 | Delay131 | | :white_check_mark: | 24 | | $e153 | Delayms | | :white_check_mark: | 25 | | $e161 | DisPFObj | | :white_check_mark: | 26 | | $e16b | EnPFObj | | :white_check_mark: | 27 | | $e171 | DisObj | | :white_check_mark: | 28 | | $e178 | EnObj | | :white_check_mark: | 29 | | $e17e | DisPF | | :white_check_mark: | 30 | | $e185 | EnPF | | :white_check_mark: | 31 | | $e18b | NMI | | :white_check_mark: | 32 | | $e1b2 | VINTWait | | :white_check_mark: | 33 | | $e1c7 | IRQ | | :white_check_mark: | 34 | | $e1f8 | LoadFiles | | | 35 | | $e237 | AppendFile | | | 36 | | $e239 | WriteFile | | | 37 | | $e2b7 | CheckFileCount | | | 38 | | $e2bb | AdjustFileCount | | | 39 | | $e301 | SetFileCount1 | | | 40 | | $e305 | SetFileCount | | | 41 | | $e32a | GetDiskInfo | | | 42 | | $e3da | AddYtoPtr0A | | | 43 | | $e3e7 | GetHardCodedPointers | | | 44 | | $e3ea | GetHardCodedPointersWriteProtected | | | 45 | | $e445 | CheckDiskHeader | | | 46 | | $e484 | GetNumFiles | | | 47 | | $e492 | SetNumFiles | | | 48 | | $e4a0 | FileMatchTest | 0 | | 49 | | $e4da | SkipFiles | 0 | | 50 | | $e4f9 | LoadData | | | 51 | | $e506 | ReadData | | | 52 | | $e5b5 | SaveData | | | 53 | | $e64d | WaitForDriveReady | | | 54 | | $e685 | StopMotor | | | 55 | | $e68f | CheckBlockType | | | 56 | | $e6b0 | WriteBlockType | | | 57 | | $e6e3 | StartXfer | | | 58 | | $e706 | EndOfBlockRead | | | 59 | | $e729 | EndOfBlkWrite | | | 60 | | $e778 | XferDone | | | 61 | | $e794 | Xfer1stByte | | | 62 | | $e7a3 | XferByte | | | 63 | | $e7bb | VRAMStructWrite | | :white_check_mark: | 64 | | $e844 | FetchDirectPtr | | :white_check_mark: | 65 | | $e86a | WriteVRAMBuffers | | :white_check_mark: | 66 | | $e8b3 | ReadIndividualVRAMBytes | | :white_check_mark: | 67 | | $e8d2 | PrepareVRAMString | | :white_check_mark: | 68 | | $e8e1 | PrepareVRAMStrings | | :white_check_mark: | 69 | | $e94f | GetVRAMBufferByte | | :white_check_mark: | 70 | | $e97d | Pixel2NamConv | | :white_check_mark: | 71 | | $e997 | Nam2PixelConv | | :white_check_mark: | 72 | | $e9b1 | Random | | :white_check_mark: | 73 | | $e9c8 | SpriteDMA | | :white_check_mark: | 74 | | $e9d3 | CounterLogic | | :white_check_mark: | 75 | | $e9eb | ReadPads | | :white_check_mark: | 76 | | $ea1a | ReadDownPads | | :white_check_mark: | 77 | | $ea1f | ReadOrDownPads | | :white_check_mark: | 78 | | $ea36 | ReadDownVerifyPads | | :white_check_mark: | 79 | | $ea4c | ReadOrDownVerifyPads | | :white_check_mark: | 80 | | $ea68 | ReadDownExpPads | | :white_check_mark: | 81 | | $ea84 | VRAMFill | | :white_check_mark: | 82 | | $ead2 | MemFill | | :white_check_mark: | 83 | | $eaea | SetScroll | | :white_check_mark: | 84 | | $eafd | JumpEngine | | :white_check_mark: | 85 | | $eb13 | ReadKeyboard | 0 | | 86 | | $ebaf | LoadTileset | | :white_check_mark: | 87 | | $ec22 | unk_EC22 | | | 88 | | $ee17 | StartMotor | | | 89 | 90 | ## Initialization 91 | 92 | # Building 93 | 94 | To build, use [asm6f](https://github.com/freem/asm6f). 95 | 96 | ```asm6f freedisksys.asm``` 97 | 98 | The `-l` flag is very useful for development - it shows the addresses assigned 99 | to each instruction, so you can easily see how much room remains for a given 100 | subroutine. 101 | 102 | # Contributing 103 | 104 | Rules: 105 | 106 | 1. Do not even look at a disassembly of the original FDS BIOS. 107 | 1. Only contribute code to which you hold the copyright or which is already under a compatible license. 108 | 109 | # License 110 | 111 | FreeDiskSysROM is licensed under the GNU LGPL v3. The intent in using this license is to allow anyone to replace the 8 KiB official FDS BIOS with FreeDiskSysROM, whether for commerical or non-commercial purposes, so long as the source of FreeDiskSysROM (including any modifications) is made available to the end-user under the same license. 112 | 113 | Although the Famicom does not have an OS or any concept of dynamic linking, the FDS BIOS is analogous to a system library in practice. FDS titles, FDS emulators, and FDS clone systems are all permitted to utilize FreeDiskSysROM without regard to or changes to the licenses of their own code. 114 | -------------------------------------------------------------------------------- /apireport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Scan the current working directory for *.fds files, and count calls 3 | # into the FDS BIOS from PRG code. 4 | 5 | import glob 6 | import os 7 | import re 8 | import stat 9 | import string 10 | import struct 11 | 12 | apis = { 13 | 0xe149: ['Delay132', 0], 14 | 0xe153: ['Delayms', 0], 15 | 0xe161: ['DisPFObj', 0], 16 | 0xe16b: ['EnPFObj', 0], 17 | 0xe171: ['DisObj', 0], 18 | 0xe178: ['EnObj', 0], 19 | 0xe17e: ['DisPF', 0], 20 | 0xe185: ['EnPF', 0], 21 | 0xe1b2: ['VINTWait', 0], 22 | 0xe1f8: ['LoadFiles', 0], 23 | 0xe237: ['AppendFile', 0], 24 | 0xe239: ['WriteFile', 0], 25 | 0xe2b7: ['CheckFileCount', 0], 26 | 0xe2bb: ['AdjustFileCount', 0], 27 | 0xe301: ['SetFileCount1', 0], 28 | 0xe305: ['SetFileCount', 0], 29 | 0xe32a: ['GetDiskInfo', 0], 30 | 0xe3da: ['AddYtoPtr0A', 0], 31 | 0xe3e7: ['GetHardCodedPointers', 0], 32 | 0xe3ea: ['GetHardCodedPointersWriteProtected', 0], 33 | 0xe445: ['CheckDiskHeader', 0], 34 | 0xe484: ['GetNumFiles', 0], 35 | 0xe492: ['SetNumFiles', 0], 36 | 0xe4a0: ['FileMatchTest', 0], 37 | 0xe4da: ['SkipFiles', 0], 38 | 0xe4f9: ['LoadData', 0], 39 | 0xe506: ['ReadData', 0], 40 | 0xe5b5: ['SaveData', 0], 41 | 0xe64d: ['WaitForDriveReady', 0], 42 | 0xe685: ['StopMotor', 0], 43 | 0xe68f: ['CheckBlockType', 0], 44 | 0xe6b0: ['WriteBlockType', 0], 45 | 0xe6e3: ['StartXfer', 0], 46 | 0xe706: ['EndOfBlockRead', 0], 47 | 0xe729: ['EndOfBlkWrite', 0], 48 | 0xe778: ['XferDone', 0], 49 | 0xe794: ['Xfer1stByte', 0], 50 | 0xe7a3: ['XferByte', 0], 51 | 0xe7bb: ['VRAMStructWrite', 0], 52 | 0xe844: ['FetchDirectPtr', 0], 53 | 0xe86a: ['WriteVRAMBuffer', 0], 54 | 0xe8b3: ['ReadVRAMBuffer', 0], 55 | 0xe8d2: ['PrepareVRAMString', 0], 56 | 0xe8e1: ['PrepareVRAMStrings', 0], 57 | 0xe94f: ['GetVRAMBufferByte', 0], 58 | 0xe97d: ['Pixel2NamConv', 0], 59 | 0xe997: ['Nam2PixelConv', 0], 60 | 0xe9b1: ['Random', 0], 61 | 0xe9c8: ['SpriteDMA', 0], 62 | 0xe9d3: ['CounterLogic', 0], 63 | 0xe9eb: ['ReadPads', 0], 64 | 0xea1a: ['ReadDownPads', 0], 65 | 0xea1f: ['ReadOrDownPads', 0], 66 | 0xea36: ['ReadDownVerifyPads', 0], 67 | 0xea4c: ['ReadOrDownVerifyPads', 0], 68 | 0xea68: ['ReadDownExpPads', 0], 69 | 0xea84: ['VRAMFill', 0], 70 | 0xead2: ['MemFill', 0], 71 | 0xeaea: ['SetScroll', 0], 72 | 0xeafd: ['JumpEngine', 0], 73 | 0xeb13: ['ReadKeyboard', 0], 74 | 0xebaf: ['LoadTileset', 0], 75 | 0xec22: ['unk_EC22', 0], 76 | 0xee17: ['StartMotor', 0], 77 | } 78 | 79 | unknown_apis = {} 80 | 81 | opcodeUnofficialTable = [ 82 | False, False, True, True, True, False, False, True, 83 | False, False, False, True, True, False, False, True, 84 | 85 | False, False, True, True, True, False, False, True, 86 | False, False, True, True, True, False, False, True, 87 | 88 | False, False, True, True, False, False, False, True, 89 | False, False, False, True, False, False, False, True, 90 | 91 | False, False, True, True, True, False, False, True, 92 | False, False, True, True, True, False, False, True, 93 | 94 | False, False, True, True, True, False, False, True, 95 | False, False, False, True, False, False, False, True, 96 | 97 | False, False, True, True, True, False, False, True, 98 | False, False, True, True, True, False, False, True, 99 | 100 | False, False, True, True, True, False, False, True, 101 | False, False, False, True, False, False, False, True, 102 | 103 | False, False, True, True, True, False, False, True, 104 | False, False, True, True, True, False, False, True, 105 | 106 | True, False, True, True, False, False, False, True, 107 | False, True, False, True, False, False, False, True, 108 | 109 | False, False, True, True, False, False, False, True, 110 | False, False, False, True, True, False, True, True, 111 | 112 | False, False, False, True, False, False, False, True, 113 | False, False, False, True, False, False, False, True, 114 | 115 | False, False, True, True, False, False, False, True, 116 | False, False, False, True, False, False, False, True, 117 | 118 | False, False, True, True, False, False, False, True, 119 | False, False, False, True, False, False, False, True, 120 | 121 | False, False, True, True, True, False, False, True, 122 | False, False, True, True, True, False, False, True, 123 | 124 | False, False, True, True, False, False, False, True, 125 | False, False, False, True, False, False, False, True, 126 | 127 | False, False, True, True, True, False, False, True, 128 | False, False, True, True, True, False, False, True, 129 | ] 130 | 131 | roms = glob.glob("*.fds") 132 | 133 | for r in roms: 134 | info = os.stat(r) 135 | disks = [] 136 | counts = {} 137 | 138 | with open(r, 'rb') as f: 139 | print(r + ':') 140 | 141 | if info.st_size % 65500 == 16: 142 | # skip optional FDS header 143 | f.seek(16) 144 | 145 | for side in range(info.st_size // 65500): 146 | disks.append(f.read(65500)) 147 | 148 | try: 149 | for disk in disks: 150 | # skip blocks 1 and 2 151 | if disk[0] != 1: 152 | raise Exception('Expected block 1, got {}'.format(disk[0])) 153 | 154 | if disk[56] != 2: 155 | raise Exception('Expected block 2, got {}'.format(disk[56])) 156 | 157 | num_files = disk[57] 158 | current_file = 0 159 | 160 | pos = 58 161 | while pos < 65500: 162 | # Now, read blocks 3 and 4 until the end of the disk 163 | header = disk[pos:pos+16] 164 | pos += 16 165 | 166 | # all zeroes at the end of the disk 167 | if header[0] == 0: 168 | break 169 | 170 | header_fields = struct.unpack(' num_files: 176 | # Disk side should end with all zeroes, but this one doesn't. Move on. 177 | break 178 | else: 179 | raise Exception('Expected a block 3, got {} before file {} of {}'.format(header_fields[0], current_file, num_files)) 180 | 181 | filesize = header_fields[5] 182 | filetype = header_fields[6] 183 | 184 | if disk[pos] != 4: 185 | raise Exception('Expected a block 4, got {} before file {} of {}'.format(disk[pos], current_file, num_files)) 186 | 187 | pos += 1 188 | 189 | contents = disk[pos:pos+filesize] 190 | pos += filesize 191 | 192 | if filetype != 0: 193 | continue 194 | 195 | # $20 == JSR, then low byte and high byte of absolute address, then following opcode 196 | matches = re.findall(b'\x20(.)([\xe0-\xff])(.)', contents) 197 | 198 | for m in matches: 199 | pieces = struct.unpack('. 15 | 16 | ; Decrement several counters in Zeropage. The first counter is a decimal 17 | ; counter 9 -> 8 -> 7 -> ... -> 1 -> 0 -> 9 -> ... Counters 1...A are simply 18 | ; decremented and stays at 0. Counters A+1...Y are decremented when the first 19 | ; counter does a 0 -> 9 transition, and stays at 0. 20 | ; Parameters: A, Y = end Zeropage address of counters, X = start zeropage address of counters 21 | ; Affects: A, X, $00 22 | API_ENTRYPOINT $e9d3 23 | CounterLogic: 24 | STX $00 ; 2 bytes 25 | DEC 0,X ; 2 bytes 26 | ; if negative, set the first counter to 9, otherwise continue 27 | BPL @countersToA ; 2 bytes 28 | LDA #$09 ; 2 bytes 29 | STA 0,X ; 2 bytes 30 | ; Do the A+1...Y counters now, since we know that the first counter rolled over 31 | @countersAplus1toY: 32 | ; there's no zp,y addressing mode, only zp,x so copy Y to X through A 33 | TYA ; 1 byte 34 | @countersToA: 35 | TAX ; 1 byte 36 | @loop: 37 | LDA 0,X ; 2 bytes 38 | ; skip counters that are already 0 39 | BEQ @looptest ; 2 bytes 40 | DEC 0,X ; 2 bytes 41 | @looptest: 42 | DEX ; 1 byte 43 | CPX $00 ; 2 bytes 44 | BNE @loop ; 2 bytes 45 | RTS ; 1 byte 46 | -------------------------------------------------------------------------------- /delay.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Fritters away 131 cycles. (NesDev Wiki says 132 cycles, but there's no way 17 | ; to waste that amount in just 10 bytes of instructions without clobbering 18 | ; something.) 19 | API_ENTRYPOINT $e149 20 | Delay131: 21 | CLC ; 1 byte, 2 cycles 22 | PHA ; 1 byte, 3 cycles 23 | LDA #$E9 ; 2 bytes, 2 cycles 24 | ; now waste 114 cycles 25 | @loop: 26 | ADC #1 ; 2 bytes, 2 cycles 27 | BCC @loop ; 2 bytes, 3 cycles except for last time when it's 2 28 | PLA ; 1 byte, 4 cycles 29 | RTS ; 1 byte, 6 cycles 30 | 31 | ; Delays roughly Y ms, affects X, Y 32 | ; If y == 0, then delay 256 ms 33 | API_ENTRYPOINT $e153 34 | Delayms: 35 | ; Every cycle is 1/1789.7725 ms. Each iteration of the outer loop spins 1790 cycles to delay 1 ms 36 | LDX #255 ; 2 bytes, 2 cycles 37 | ; first inner loop burns 1274 cycles 38 | @inner1: 39 | DEX ; 1 byte, 2 cycles 40 | BNE @inner1 ; 2 bytes, 3 cycles except for the last time when it's 2 41 | LDX #102 ; 2 bytes, 2 cycles 42 | ; second inner loop burns 509 cycles (ideal would be 507) 43 | @inner2: 44 | DEX ; 1 byte, 2 cycles 45 | BNE @inner2 ; 2 bytes, 3 cycles except for the last time when it's 2 46 | DEY ; 1 byte, 2 cycles 47 | BNE Delayms ; 2 bytes, 3 cycles except for the last time when it's 2 48 | RTS ; 1 byte, 6 cycles 49 | -------------------------------------------------------------------------------- /fetchdirectptr.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Fetch a direct pointer located at the apparent return address of the routine 17 | ; that calls this one, save the pointer at ($00), and fix the return address. 18 | ; Assumes nothing more was pushed onto the stack by the caller. 19 | ; Affects: A, X, Y, $05, $06 20 | ; Returns: $00, $01 = pointer fetched 21 | API_ENTRYPOINT $e844 22 | FetchDirectPtr: 23 | ; The stack pointer is currently 1 below the return address of the caller. 24 | ; We need the return address of the caller's caller, three bytes later. 25 | TSX 26 | LDA $103,X 27 | STA $05 28 | LDY $104,X 29 | STY $06 30 | CLC 31 | ADC #2 ; fix return address of caller's caller (it's the fetched pointer + 2) 32 | BCC @storeFixedPointer 33 | INY ; add the carry 34 | @storeFixedPointer: 35 | STA $103,X ; write low byte of fixed return address to stack 36 | TYA ; no abs,x addressing mode for STY, so transfer Y to A 37 | STA $104,X ; write high byte of fixed return address to stack 38 | LDY #1 ; the return address is always 1 less than the real location 39 | LDA ($05),Y ; get low byte of direct pointer 40 | STA $00 ; store low byte of direct pointer 41 | INY 42 | LDA ($05),Y ; get high byte of direct pointer 43 | STA $01 ; store high byte of direct pointer 44 | RTS 45 | -------------------------------------------------------------------------------- /font.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2020 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; The actual binary font data in this file is not copyrighted, as no bitmap 17 | ; font is copyrightable. 18 | 19 | ; The characters in the FDS font are 1 bpp, not 2 bpp as you might expect. 20 | ; Each byte of the character is copied twice to fill out the two bitplanes in 21 | ; CHR-RAM. 22 | 23 | ; If you kinda squint, you can see the characters in the 1s and 0s below, since 24 | ; it's in binary. The hex representation of this data is not useful. 25 | 26 | ; 0 27 | DB 00111000b 28 | DB 01001100b 29 | DB 11000110b 30 | DB 11000110b 31 | DB 11000110b 32 | DB 01100100b 33 | DB 00111000b 34 | DB 00000000b 35 | 36 | ; 1 37 | DB 00011000b 38 | DB 00111000b 39 | DB 00011000b 40 | DB 00011000b 41 | DB 00011000b 42 | DB 00011000b 43 | DB 01111110b 44 | DB 00000000b 45 | 46 | ; 2 47 | DB 01111100b 48 | DB 11000110b 49 | DB 00001110b 50 | DB 00111100b 51 | DB 01111000b 52 | DB 11100000b 53 | DB 11111110b 54 | DB 00000000b 55 | 56 | ; 3 57 | DB 01111110b 58 | DB 00001100b 59 | DB 00011000b 60 | DB 00111100b 61 | DB 00000110b 62 | DB 11000110b 63 | DB 01111100b 64 | DB 00000000b 65 | 66 | ; 4 67 | DB 00011100b 68 | DB 00111100b 69 | DB 01101100b 70 | DB 11001100b 71 | DB 11111110b 72 | DB 00001100b 73 | DB 00001100b 74 | DB 00000000b 75 | 76 | ; 5 77 | DB 11111100b 78 | DB 11000000b 79 | DB 11111100b 80 | DB 00000110b 81 | DB 00000110b 82 | DB 11000110b 83 | DB 01111100b 84 | DB 00000000b 85 | 86 | ; 6 87 | DB 00111100b 88 | DB 01100000b 89 | DB 11000000b 90 | DB 11111100b 91 | DB 11000110b 92 | DB 11000110b 93 | DB 01111100b 94 | DB 00000000b 95 | 96 | ; 7 97 | DB 11111110b 98 | DB 11000110b 99 | DB 00001100b 100 | DB 00011000b 101 | DB 00110000b 102 | DB 00110000b 103 | DB 00110000b 104 | DB 00000000b 105 | 106 | ; 8 107 | DB 01111100b 108 | DB 11000110b 109 | DB 11000110b 110 | DB 01111100b 111 | DB 11000110b 112 | DB 11000110b 113 | DB 01111100b 114 | DB 00000000b 115 | 116 | ; 9 117 | DB 01111100b 118 | DB 11000110b 119 | DB 11000110b 120 | DB 01111110b 121 | DB 00000110b 122 | DB 00001100b 123 | DB 01111000b 124 | DB 00000000b 125 | 126 | ; A 127 | DB 00111000b 128 | DB 01101100b 129 | DB 11000110b 130 | DB 11000110b 131 | DB 11111110b 132 | DB 11000110b 133 | DB 11000110b 134 | DB 00000000b 135 | 136 | ; B 137 | DB 11111100b 138 | DB 11000110b 139 | DB 11000110b 140 | DB 11111100b 141 | DB 11000110b 142 | DB 11000110b 143 | DB 11111100b 144 | DB 00000000b 145 | 146 | ; C 147 | DB 00111100b 148 | DB 01100110b 149 | DB 11000000b 150 | DB 11000000b 151 | DB 11000000b 152 | DB 01100110b 153 | DB 00111100b 154 | DB 00000000b 155 | 156 | ; D 157 | DB 11111000b 158 | DB 11001100b 159 | DB 11000110b 160 | DB 11000110b 161 | DB 11000110b 162 | DB 11001100b 163 | DB 11111000b 164 | DB 00000000b 165 | 166 | ; E 167 | DB 11111110b 168 | DB 11000000b 169 | DB 11000000b 170 | DB 11111100b 171 | DB 11000000b 172 | DB 11000000b 173 | DB 11111110b 174 | DB 00000000b 175 | 176 | ; F 177 | DB 11111110b 178 | DB 11000000b 179 | DB 11000000b 180 | DB 11111100b 181 | DB 11000000b 182 | DB 11000000b 183 | DB 11000000b 184 | DB 00000000b 185 | 186 | ; G 187 | DB 00111110b 188 | DB 01100000b 189 | DB 11000000b 190 | DB 11011110b 191 | DB 11000110b 192 | DB 01100110b 193 | DB 01111110b 194 | DB 00000000b 195 | 196 | ; H 197 | DB 11000110b 198 | DB 11000110b 199 | DB 11000110b 200 | DB 11111110b 201 | DB 11000110b 202 | DB 11000110b 203 | DB 11000110b 204 | DB 00000000b 205 | 206 | ; I 207 | DB 01111110b 208 | DB 00011000b 209 | DB 00011000b 210 | DB 00011000b 211 | DB 00011000b 212 | DB 00011000b 213 | DB 01111110b 214 | DB 00000000b 215 | 216 | ; J 217 | DB 00011110b 218 | DB 00000110b 219 | DB 00000110b 220 | DB 00000110b 221 | DB 11000110b 222 | DB 11000110b 223 | DB 01111100b 224 | DB 00000000b 225 | 226 | ; K 227 | DB 11000110b 228 | DB 11001100b 229 | DB 11011000b 230 | DB 11110000b 231 | DB 11111000b 232 | DB 11011100b 233 | DB 11001110b 234 | DB 00000000b 235 | 236 | ; L 237 | DB 01100000b 238 | DB 01100000b 239 | DB 01100000b 240 | DB 01100000b 241 | DB 01100000b 242 | DB 01100000b 243 | DB 01111110b 244 | DB 00000000b 245 | 246 | ; M 247 | DB 11000110b 248 | DB 11101110b 249 | DB 11111110b 250 | DB 11111110b 251 | DB 11010110b 252 | DB 11000110b 253 | DB 11000110b 254 | DB 00000000b 255 | 256 | ; N 257 | DB 11000110b 258 | DB 11100110b 259 | DB 11110110b 260 | DB 11111110b 261 | DB 11011110b 262 | DB 11001110b 263 | DB 11000110b 264 | DB 00000000b 265 | 266 | ; O 267 | DB 01111100b 268 | DB 11000110b 269 | DB 11000110b 270 | DB 11000110b 271 | DB 11000110b 272 | DB 11000110b 273 | DB 01111100b 274 | DB 00000000b 275 | 276 | ; P 277 | DB 11111100b 278 | DB 11000110b 279 | DB 11000110b 280 | DB 11000110b 281 | DB 11111100b 282 | DB 11000000b 283 | DB 11000000b 284 | DB 00000000b 285 | 286 | ; Q 287 | DB 01111100b 288 | DB 11000110b 289 | DB 11000110b 290 | DB 11000110b 291 | DB 11011110b 292 | DB 11001100b 293 | DB 01111010b 294 | DB 00000000b 295 | 296 | ; R 297 | DB 11111100b 298 | DB 11000110b 299 | DB 11000110b 300 | DB 11001110b 301 | DB 11111000b 302 | DB 11011100b 303 | DB 11001110b 304 | DB 00000000b 305 | 306 | ; S 307 | DB 01111000b 308 | DB 11001100b 309 | DB 11000000b 310 | DB 01111100b 311 | DB 00000110b 312 | DB 11000110b 313 | DB 01111100b 314 | DB 00000000b 315 | 316 | ; T 317 | DB 01111110b 318 | DB 00011000b 319 | DB 00011000b 320 | DB 00011000b 321 | DB 00011000b 322 | DB 00011000b 323 | DB 00011000b 324 | DB 00000000b 325 | 326 | ; U 327 | DB 11000110b 328 | DB 11000110b 329 | DB 11000110b 330 | DB 11000110b 331 | DB 11000110b 332 | DB 11000110b 333 | DB 01111100b 334 | DB 00000000b 335 | 336 | ; V 337 | DB 11000110b 338 | DB 11000110b 339 | DB 11000110b 340 | DB 11101110b 341 | DB 01111100b 342 | DB 00111000b 343 | DB 00010000b 344 | DB 00000000b 345 | 346 | ; W 347 | DB 11000110b 348 | DB 11000110b 349 | DB 11010110b 350 | DB 11111110b 351 | DB 11111110b 352 | DB 11101110b 353 | DB 11000110b 354 | DB 00000000b 355 | 356 | ; X 357 | DB 11000110b 358 | DB 11101110b 359 | DB 01111100b 360 | DB 00111000b 361 | DB 01111100b 362 | DB 11101110b 363 | DB 11000110b 364 | DB 00000000b 365 | 366 | ; Y 367 | DB 01100110b 368 | DB 01100110b 369 | DB 01100110b 370 | DB 00111100b 371 | DB 00011000b 372 | DB 00011000b 373 | DB 00011000b 374 | DB 00000000b 375 | 376 | ; Z 377 | DB 11111110b 378 | DB 00001110b 379 | DB 00011100b 380 | DB 00111000b 381 | DB 01110000b 382 | DB 11100000b 383 | DB 11111110b 384 | DB 00000000b 385 | 386 | ; space 387 | DB 00000000b 388 | DB 00000000b 389 | DB 00000000b 390 | DB 00000000b 391 | DB 00000000b 392 | DB 00000000b 393 | DB 00000000b 394 | DB 00000000b 395 | 396 | ; , 397 | DB 00000000b 398 | DB 00000000b 399 | DB 00000000b 400 | DB 00000000b 401 | DB 00110000b 402 | DB 00110000b 403 | DB 00100000b 404 | DB 00000000b 405 | 406 | ; . 407 | DB 00000000b 408 | DB 00000000b 409 | DB 00000000b 410 | DB 00000000b 411 | DB 00110000b 412 | DB 00110000b 413 | DB 00000000b 414 | DB 00000000b 415 | 416 | ; ., 417 | DB 00000000b 418 | DB 00000000b 419 | DB 00000000b 420 | DB 00000000b 421 | DB 01101100b 422 | DB 01101100b 423 | DB 00001000b 424 | DB 00000000b 425 | 426 | ; registered trademark symbol 427 | DB 00111000b 428 | DB 01000100b 429 | DB 10111010b 430 | DB 10101010b 431 | DB 10110010b 432 | DB 10101010b 433 | DB 01000100b 434 | DB 00111000b 435 | -------------------------------------------------------------------------------- /freedisksys.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; zero-page registers 17 | ZP_PPUCTRL EQU $FF; value last written to $2000 $80 on reset. 18 | ZP_PPUMASK EQU $FE; value last written to $2001 $06 on reset 19 | ZP_PPUSCROLL1 EQU $FD; value last written to $2005/1 $00 on reset. 20 | ZP_PPUSCROLL2 EQU $FC; value last written to $2005/2 $00 on reset. 21 | ZP_JOYPAD1 EQU $FB; value last written to $4016 $00 on reset. 22 | ZP_FDSCTRL EQU $FA; value last written to $4025 $2E on reset. 23 | ZP_EXTCONN EQU $F9; value last written to $4026 $FF on reset. 24 | 25 | ; [$0102]/[$0103]: PC action on reset 26 | 27 | ; PPU registers 28 | PPUCTRL EQU $2000 29 | PPUMASK EQU $2001 30 | PPUSTATUS EQU $2002 31 | OAMADDR EQU $2003 32 | OAMDATA EQU $2004 33 | PPUSCROLL EQU $2005 34 | PPUADDR EQU $2006 35 | PPUDATA EQU $2007 36 | OAMDMA EQU $4014 37 | 38 | ; undocumented instructions not explicitly supported by asm6f 39 | ; TOP (abs) 40 | MACRO TOP address 41 | DB $0C 42 | DW #address 43 | ENDM 44 | 45 | ; Input registers 46 | JOYPAD1 EQU $4016 47 | JOYPAD2 EQU $4017 48 | 49 | ; FDS registers 50 | IRQLOW EQU $4020 51 | IRQHIGH EQU $4021 52 | IRQCTRL EQU $4022 53 | MASTERIO EQU $4023 54 | WRITEDATA EQU $4024 55 | FDSCTRL EQU $4025 56 | EXTCONNWR EQU $4026 57 | DISKSTATUS EQU $4030 58 | READDATA EQU $4031 59 | DRIVESTATUS EQU $4032 60 | EXTCONNRD EQU $4033 61 | 62 | ; Error codes: 63 | OK EQU $00 ; no error 64 | DISK_NOT_SET EQU $01 ; disk set, ($4032.0) disk not set 65 | POWER_SUPPLY_FAILURE EQU $02 ; battery, ($4033.7) power supply failure 66 | WRITE_PROTECTED EQU $03 ; ($4032.2) disk is write protected 67 | WRONG_MAKER_ID EQU $04 ; Wrong maker ID 68 | WRONG_GAME EQU $05 ; Wrong game 69 | WRONG_GAME_VER EQU $06 ; Wrong game version 70 | WRONG_SIDE_NUM EQU $07 ; a,b side, wrong side number 71 | WRONG_DISK_NUM EQU $08 ; disk no., wrong disk number 72 | WRONG_ADDL_DISK_ID1 EQU $09 ; wrong additional disk ID 1 73 | WRONG_ADDL_DISK_ID2 EQU $0a ; wrong additional disk ID 2 74 | APPROVAL_CHECK_FAILED EQU $20 ; disk trouble, approval check failed 75 | WRONG_SIGNATURE EQU $21 ; disk trouble, '*NINTENDO-HVC*' string in block 1 doesn't match 76 | BLOCK_TYPE_1_EXPECTED EQU $22 ; disk trouble, block type 1 expected 77 | BLOCK_TYPE_2_EXPECTED EQU $23 ; disk trouble, block type 2 expected 78 | BLOCK_TYPE_3_EXPECTED EQU $24 ; disk trouble, block type 3 expected 79 | BLOCK_TYPE_4_EXPECTED EQU $25 ; disk trouble, block type 4 expected 80 | BLOCK_FAILED_CRC EQU $27 ; disk trouble, ($4030.4) block failed CRC 81 | EOF_READ EQU $28 ; disk trouble, ($4030.6) file ends prematurely during read 82 | EOF_WRITE EQU $29 ; disk trouble, ($4030.6) file ends prematurely during write 83 | DISK_FULL EQU $30 ; disk trouble, ($4032.1) disk is full 84 | 85 | MACRO API_ENTRYPOINT address 86 | IF $ > #address 87 | ERROR "Previous function overflowed into following public API" 88 | ELSE 89 | PAD #address 90 | ENDIF 91 | ENDM 92 | 93 | ; Fill with a KIL opcode, so we die immediately if we jump into crazytown 94 | FILLVALUE $72 95 | 96 | ; Start assembling at the beginning of the FDS ROM area 97 | ORG $E000 98 | DB $00 99 | 100 | INCLUDE font.asm 101 | INCLUDE delay.asm 102 | INCLUDE ppumask.asm 103 | INCLUDE nmi.asm 104 | INCLUDE irq.asm 105 | 106 | ; Loads files specified by DiskID into memory from disk. Load addresses are 107 | ; decided by the file's header. 108 | ; Parameters: Pointer to Disk ID, Pointer to File List 109 | ; Returns: A = error #, Y = # of files loaded 110 | API_ENTRYPOINT $e1f8 111 | LoadFiles: 112 | RTS 113 | 114 | ; Appends the file data given by DiskID to the disk. This means that the file 115 | ; is tacked onto the end of the disk, and the disk file count is incremented. 116 | ; The file is then read back to verify the write. If an error occurs during 117 | ; verification, the disk's file count is decremented (logically hiding the 118 | ; written file). 119 | ; Parameters: Pointer to Disk ID, Pointer to File Header 120 | ; Returns: A = error # 121 | API_ENTRYPOINT $e237 122 | AppendFile: 123 | RTS 124 | 125 | ; Same as "Append File", but instead of writing the file to the end of the 126 | ; disk, A specifies the sequential position on the disk to write the file (0 127 | ; is the first). This also has the effect of setting the disk's file count to 128 | ; the A value, therefore logically hiding any other files that may reside after 129 | ; the written one. 130 | ; Parameters: Pointer to Disk ID, Pointer to File Header, A = file # 131 | ; Returns: A = error # 132 | API_ENTRYPOINT $e239 133 | WriteFile: 134 | RTS 135 | 136 | ; Reads in disk's file count, compares it to A, then sets the disk's file count 137 | ; to A. 138 | ; Parameters: Pointer to Disk ID, A = # to set file count to 139 | ; Returns: A = error # 140 | API_ENTRYPOINT $e2b7 141 | CheckFileCount: 142 | RTS 143 | 144 | ; Reads in disk's file count, decrements it by A, then writes the new value 145 | ; back. 146 | ; Parameters: Pointer to Disk ID, A = number to reduce current file count by 147 | ; Returns: A = error # 148 | API_ENTRYPOINT $e2bb 149 | AdjustFileCount: 150 | RTS 151 | 152 | ; Set the file count to A + 1 153 | ; Parameters: Pointer to Disk ID, A = file count minus one = # of the last file 154 | ; Returns: A = error # 155 | API_ENTRYPOINT $e301 156 | SetFileCount1: 157 | RTS 158 | 159 | ; Set the file count to A 160 | ; Parameters: Pointer to Disk ID, A = file count 161 | ; Returns: A = error # 162 | API_ENTRYPOINT $e305 163 | SetFileCount: 164 | RTS 165 | 166 | ; Fills provided DiskInfo structure with data read off the current disk. 167 | ; Parameters: Pointer to Disk Info 168 | ; Returns: A = error # 169 | API_ENTRYPOINT $e32a 170 | GetDiskInfo: 171 | RTS 172 | 173 | API_ENTRYPOINT $e3da 174 | AddYtoPtr0A: 175 | RTS 176 | 177 | INCLUDE gethardcodedpointers.asm 178 | 179 | ; Compares the first 10 bytes on the disk coming after the FDS string, to 10 180 | ; bytes pointed to by Ptr($00). To bypass the checking of any byte, a -1 can be 181 | ; placed in the equivelant place in the compare string. Otherwise, if the 182 | ; comparison fails, an appropriate error will be generated. 183 | ; Parameters: Pointer to 10 byte string at $00 184 | API_ENTRYPOINT $e445 185 | CheckDiskHeader: 186 | RTS 187 | 188 | ; Reads number of files stored on disk, stores the result in $06 189 | API_ENTRYPOINT $e484 190 | GetNumFiles: 191 | RTS 192 | 193 | ; Writes new number of files to disk header. 194 | ; Parameters: A = number of files 195 | API_ENTRYPOINT $e492 196 | SetNumFiles: 197 | RTS 198 | 199 | ; Uses a byte string pointed at by Ptr($02) to tell the disk system which files 200 | ; to load. The file ID's number is searched for in the string. If an exact 201 | ; match is found, [$09] is 0'd, and [$0E] is incremented. If no matches are 202 | ; found after 20 bytes, or a -1 entry is encountered, [$09] is set to -1. If 203 | ; the first byte in the string is -1, the BootID number is used for matching 204 | ; files (any FileID that is not greater than the BootID qualifies as a match). 205 | ; Parameters: Pointer to FileID list at $02 206 | API_ENTRYPOINT $e4a0 207 | FileMatchTest: 208 | RTS 209 | 210 | ; Skips over specified number of files. 211 | ; Parameters: Number of files to skip in $06 212 | API_ENTRYPOINT $e4da 213 | SkipFiles: 214 | RTS 215 | 216 | API_ENTRYPOINT $e4f9 217 | LoadData: 218 | RTS 219 | 220 | API_ENTRYPOINT $e506 221 | ReadData: 222 | RTS 223 | 224 | API_ENTRYPOINT $e5b5 225 | SaveData: 226 | RTS 227 | 228 | API_ENTRYPOINT $e64d 229 | WaitForDriveReady: 230 | RTS 231 | 232 | API_ENTRYPOINT $e685 233 | StopMotor: 234 | RTS 235 | 236 | API_ENTRYPOINT $e68f 237 | CheckBlockType: 238 | RTS 239 | 240 | API_ENTRYPOINT $e6b0 241 | WriteBlockType: 242 | RTS 243 | 244 | API_ENTRYPOINT $e6e3 245 | StartXfer: 246 | RTS 247 | 248 | API_ENTRYPOINT $e706 249 | EndOfBlockRead: 250 | RTS 251 | 252 | API_ENTRYPOINT $e729 253 | EndOfBlkWrite: 254 | RTS 255 | 256 | API_ENTRYPOINT $e778 257 | XferDone: 258 | RTS 259 | 260 | ; Waits for the first byte to be transferred between the drive and RAM adapter. 261 | ; Does not know or care whether it's a read or write. An interrupt is involved, 262 | ; but the stack is manipulated in the ISR such that control returns to the 263 | ; caller of this function as if it were a simple subroutine. 264 | ; Parameters: A = byte to write to disk (if this is a write) 265 | ; Affects: X, $101, $FA 266 | ; Returns: A = byte read from disk (if this is a read) 267 | API_ENTRYPOINT $e794 268 | Xfer1stByte: 269 | RTS 270 | 271 | ; Waits for a byte to be transferred between the drive and the RAM adapter. 272 | ; Does not know or care whether it's a read or write. An interrupt is involved, 273 | ; but the stack is manipulated in the ISR such that control returns to the 274 | ; caller of this function as if it were a simple subroutine. 275 | ; Parameters: A = byte to write to disk (if this is a write) 276 | ; Affects: X 277 | ; Returns: A = byte read from disk (if this is a read) 278 | API_ENTRYPOINT $e7a3 279 | XferByte: 280 | RTS 281 | 282 | ; VRAM Buffers 283 | ; The structure of VRAM buffers are as follows: 284 | ; 285 | ; SIZE CONTENTS 286 | ; 2 VRAM Address (big endian) 287 | ; 1 bit 0-5 length of data ($0 means a length of 64) 288 | ; bit 6 : 0 = copy, 1 = fill 289 | ; bit 7 : 0 = increment by 1, 1 = increment by 32 290 | ; n Data to copy to VRAM 291 | ; ..... repeated as many times as needed 292 | ; 1 $ff 293 | ; 294 | ; * The main structure is terminated by a byte >= $80 (High address is always 295 | ; supposed to be in $00..$3f range) 296 | ; * $4c is a "call" command. The 2 bytes that follow is the address of a sub- 297 | ; VRAM structure. The sub-structure can call another sub-structure and so on. 298 | ; * $60 is a "return" command. It will terminate a sub-structure. 299 | ; * If Fill mode is used, the routine takes only 1 byte of data which is 300 | ; repeated. 301 | ; 302 | ; The VRAM buffer is located at $300-$3xx. $300 holds the size of the buffer 303 | ; (maximum), and $301 holds the end index of the buffer. The actual buffer lies 304 | ; at $302-$3xx, and is of variable length. 305 | ; 306 | ; * $300 is initialized to the value $7d, effectively making the buffer lie at 307 | ; $300-$37f. It's possible to change the value here to make it bigger or 308 | ; smaller, but the biggest possible value is $fd, making the buffer lie at 309 | ; $300-$3ff. 310 | ; * Format of the buffer is equivalent to the VRAM structure above, except that 311 | ; there are no sub-structures, no increment by 32 flag and no fill flag. 312 | ; * For this reason, the VRAM buffer at $302 can be used as a sub-structure. 313 | ; * A call to WriteVRAMBuffers will execute faster than a call to 314 | ; VRAMStructWrite with $302 as an argument, but both will have the same 315 | ; effect. 316 | ; 317 | ; The structure of the VRAM read buffer itself is trivial - only single bytes 318 | ; are read (there's no runs of data). All reads are mapped to a structure of 3 319 | ; bytes in the read buffer: 320 | ; 321 | ; SIZE CONTENTS 322 | ; 2 VRAM Address (big endian) 323 | ; 1 data 324 | ; 325 | ; Therfore, for each byte which is read from VRAM, 3 bytes have to be reserved 326 | ; in the read buffer. Once data from VRAM has been read, if it must be written 327 | ; back after a modification, the user need to copy it to the write buffer 328 | ; manually. 329 | 330 | INCLUDE vramstructwrite.asm 331 | INCLUDE fetchdirectptr.asm 332 | INCLUDE vrambuffers.asm 333 | INCLUDE vramstrings.asm 334 | INCLUDE getvrambufferbyte.asm 335 | INCLUDE nametable.asm 336 | INCLUDE random.asm 337 | 338 | ; Run Sprite DMA from RAM $200-$2FF 339 | ; Affects: A 340 | API_ENTRYPOINT $e9c8 341 | SpriteDMA: 342 | LDA #0 343 | STA OAMADDR 344 | LDA #$02 345 | STA OAMDMA 346 | RTS 347 | 348 | INCLUDE counterlogic.asm 349 | INCLUDE gamepads.asm 350 | INCLUDE vramfill.asm 351 | 352 | ; Fill RAM pages with specified value. 353 | ; Parameters: A = fill value, X = first page #, Y = last page # 354 | ; Affects: A, X, Y, $00, $01 355 | API_ENTRYPOINT $ead2 356 | MemFill: 357 | ; Start from the high end and count down 358 | ; The address of the start of the last page is just the Y register in the high byte of the pointer and 0 in the low byte 359 | STY $01 360 | @outerloop: 361 | LDY #0 362 | STY $00 363 | @loop: 364 | STA ($00),Y 365 | INY 366 | BNE @loop 367 | DEC $01 ; work on the next page down 368 | CPX $01 369 | BMI @outerloop ; cover the X < $01 case 370 | BEQ @outerloop ; cover the X == $01 case 371 | RTS 372 | 373 | ; Set scroll registers according to values in $FC, $FD and $FF. 374 | ; Should typically be called in VBlank after VRAM updates 375 | ; Parameters: $FC, $FD, $FF 376 | ; Affects: A 377 | API_ENTRYPOINT $eaea 378 | SetScroll: 379 | LDA PPUSTATUS ; reset PPU's "w" register to 0 380 | LDA ZP_PPUSCROLL1 381 | STA PPUSCROLL 382 | LDA ZP_PPUSCROLL2 383 | STA PPUSCROLL 384 | LDA ZP_PPUCTRL 385 | STA PPUCTRL 386 | RTS 387 | 388 | ; The instruction calling this is supposed to be followed by a jump table 389 | ; (16-bit pointers little endian, up to 128 pointers). A is the entry # to jump 390 | ; to, return address on stack is used to get jump table entries. 391 | ; Parameters: A = Jump table entry 392 | ; Affects: A, X, Y, $00, $01 393 | API_ENTRYPOINT $eafd 394 | JumpEngine: 395 | ; the top of the stack is 1 less than the address of the table 396 | SEC ; to rotate a 1 into the LSB, set the carry 397 | ROL A ; Each entry is 2 bytes, so multiply A by two to get the offset 398 | TAY ; now A is freed up and Y has the offset, which will be useful later 399 | ; get the address of the jump table 400 | PLA ; low byte of the jump table address 401 | STA $00 402 | PLA ; high byte of the jump table address 403 | STA $01 404 | ; post-indexed indirect load of the entry in the jump table 405 | LDA ($00),Y 406 | TAX 407 | INY 408 | LDA ($00),Y 409 | STX $00 410 | STA $01 411 | ; (indirect) jump! 412 | JMP ($00) 413 | 414 | ; Read Family Basic Keyboard expansion 415 | API_ENTRYPOINT $eb13 416 | ReadKeyboard: 417 | RTS 418 | 419 | INCLUDE loadtileset.asm 420 | 421 | ; Some kind of logic that some games use. (detail is under analysis) 422 | ; Parameters: $00-$01 Pointer to structure... ? 423 | ; Affects: A, X, Y, $02, $03, $04, $05, $06, $07, $08, $09 424 | API_ENTRYPOINT $ec22 425 | unk_EC22: 426 | RTS 427 | 428 | API_ENTRYPOINT $ee17 429 | StartMotor: 430 | RTS 431 | 432 | ; private functions 433 | 434 | ; Checks whether the little-endian address provided in ($02) plus the offset in 435 | ; $04 is in the range $3Fxx (or one of its mirrors). Checks the current PPU 436 | ; increment mode to do the correct arithmetic. If the current PPU address is in 437 | ; the palette, reset to $0000. Assumes that the PPUADDR latch is currently 438 | ; clear. 439 | ; Parameters: $00-$01 = PPU address, $02 = offset 440 | ; Affects: A, X, Y 441 | PreventPalettePpuAddr: 442 | LDA #%00000100 ; increment mode flag bit test 443 | AND ZP_PPUCTRL 444 | BEQ @incr1 445 | LDA $02 ; low address byte 446 | LDY $03 ; high address byte 447 | LDX $04 ; offset 448 | BEQ @check ; if the offset is 0, jump straight to the check 449 | @incr32: 450 | CLC 451 | ADC #32 ; for (X = $04; X != 0; X--) $02,$03 += 32; 452 | BCC @nextiter 453 | INY 454 | @nextiter: 455 | DEX 456 | BNE @incr32 457 | BEQ @check 458 | @incr1: 459 | LDA $02 ; low address byte 460 | LDY $03 ; high address byte 461 | CLC 462 | ADC $04 ; add length to low address byte 463 | BCC @check 464 | INY ; add C to high address byte 465 | @check: 466 | TYA 467 | AND #$3F ; we might be in the mirror $7Fxx range instead of $3Fxx 468 | CMP #$3F ; now check for exactly $3F 469 | BNE @done 470 | LDA #0 ; reset PPUADDR to 0 to get it out of the palette area 471 | STA PPUADDR 472 | STA PPUADDR 473 | @done: 474 | RTS 475 | 476 | INCLUDE reset.asm 477 | 478 | ; the hard-coded interrupt vectors at the end of ROM 479 | API_ENTRYPOINT $fffa 480 | DW NMI 481 | DW RESET 482 | DW IRQ 483 | -------------------------------------------------------------------------------- /gamepads.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Read hard-wired Famicom joypads. 17 | ; Returns: $F5 = Joypad #1 data, $F6 = Joypad #2 data, $00 = Expansion #1 data, $01 = Expansion #2 data 18 | ; Affects: A, X, $00, $01, $F5, $F6, $FB 19 | API_ENTRYPOINT $e9eb 20 | ReadPads: 21 | ; If this is being called to also read expansion port pads, bit 1 of $FB 22 | ; will be 1, otherwise it will be 0. To strobe the controllers in either 23 | ; case, just set the 1s bit on the value in $FB, then clear it to read. 24 | LDX ZP_JOYPAD1 25 | INX ; now X[0] is 1, assuming that no other function will set that bit to 1 26 | ; While the strobe bit is set, buttons will be continuously reloaded. 27 | ; This means that reading from JOYPAD1 will only return the state of the 28 | ; first button: button A. 29 | STX JOYPAD1 30 | DEX ; now X[0] is 0 31 | ; By storing bit 0 into JOYPAD1, the strobe bit is cleared and the reloading stops. 32 | ; This allows all 8 buttons (newly reloaded) to be read from JOYPAD1. 33 | STX JOYPAD1 34 | LDX #8 ; 8 bits to load 35 | @loop: 36 | LDA JOYPAD1 37 | LSR A ; if bit 0 was a 1, the carry flag will be set 38 | ROR $F5 ; rotate the carry flag's value into $F5, where the joypad #1 data lives 39 | LSR A ; bit 0 was already shifted out, now shift bit 1 into the carry flag 40 | ROR $00 ; rotate the carry flag's value into $00, where the expansion #1 data lives 41 | LDA JOYPAD2 42 | LSR A ; if bit 0 was a 1, the carry flag will be set 43 | ROR $F6 ; rotate the carry flag's value into $F6, where the joypad #2 data lives 44 | LSR A ; bit 0 was already shifted out, now shift bit 1 into the carry flag 45 | ROR $01 ; rotate the carry flag's value into $01, where the expansion #2 data lives 46 | DEX 47 | BNE @loop 48 | RTS 49 | 50 | ; Combine the reports from the built-in and expansion gamepads by OR'ing them 51 | ; together, storing them in the built-in gamepads' variables ($F5 and $F6) 52 | OrPads: 53 | LDA $00 54 | ORA $F5 55 | STA $F5 56 | LDA $01 57 | ORA $F6 58 | STA $F6 59 | RTS 60 | 61 | ; Read hard-wired Famicom joypads, and detect up->down button transitions 62 | ; Returns: $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data 63 | ; Affects: A, X, Y, $00, $01, $F5, $F6, $F7, $F8 64 | API_ENTRYPOINT $ea1a 65 | ReadDownPads: 66 | JSR ReadPads 67 | BEQ DetectUpToDownTransitions; skip over ReadOrDownPads 68 | 69 | ; Read both hard-wired Famicom and expansion port joypads (OR'd together), and detect up->down 70 | ; button transitions. 71 | ; Returns: $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data 72 | ; Affects: A, X, Y, $00, $01, $F5, $F6, $F7, $F8 73 | API_ENTRYPOINT $ea1f 74 | ReadOrDownPads: 75 | JSR ReadPads 76 | JSR OrPads 77 | ; fall through to detection 78 | 79 | DetectUpToDownTransitions: 80 | LDX #1 ; handle pad #2 first 81 | DetectUpToDownOnePad: 82 | LDA $F5,X ; load current joypad state 83 | TAY ; preserve the current state in Y 84 | EOR $F7,X ; exclusive-OR between previous and current state says which buttons have changed 85 | AND $F5,X ; AND of that with current state says which buttons have changed to down 86 | STA $F5,X ; save up-down transitions 87 | STY $F7,X ; overwrite previous state with current state via temporary 88 | DEX 89 | BPL DetectUpToDownOnePad ; when X underflows to -127, this branch will not be taken 90 | RTS 91 | 92 | ; Read hard-wired Famicom joypads, and detect up->down button transitions. Data 93 | ; is read until two consecutive read matches to work around the DMC reading 94 | ; glitches. 95 | ; Returns: $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data 96 | ; Affects: A, X, Y, $00, $01, $F5, $F6, $F7, $F8 97 | API_ENTRYPOINT $ea36 98 | ReadDownVerifyPads: 99 | JSR ReadPads 100 | @remember: 101 | LDA $F5 ; get controller #1's value 102 | PHA ; store controller #1's value on the stack 103 | LDY $F6 ; ReadPads does not touch Y, so controller #2's value can be preserved there 104 | JSR ReadPads 105 | PLA 106 | EOR $F5 ; do the previous and current reads of JOYPAD1 match? 107 | BNE @remember ; if they don't match, read again 108 | CPY $F6 ; do the previous and current reads of JOYPAD2 match? 109 | BNE @remember ; if they don't match, read again 110 | BEQ DetectUpToDownTransitions ; if they do match, branch (using BEQ which we know will work) to DetectUpToDownTransitions 111 | 112 | ; Read both hard-wired Famicom and expansion port joypads and detect up->down 113 | ; button transitions. Data is read until two consecutive read matches to work 114 | ; around the DMC reading glitches. 115 | ; Returns: $f5 = Joypad #1 up->down transitions, $f6 = Joypad #2 up->down transitions $f7 = Joypad #1 data, $f8 = Joypad #2 data 116 | ; Affects: A, X, Y, $00, $01, $F5, $F6, $F7, $F8 117 | API_ENTRYPOINT $ea4c 118 | ReadOrDownVerifyPads: 119 | JSR ReadPads 120 | @remember: 121 | LDA $F5 ; get controller #1's value 122 | PHA ; store controller #1's value on the stack 123 | LDY $F6 ; ReadPads does not touch Y, so controller #2's value can be preserved there 124 | JSR ReadPads 125 | PLA 126 | EOR $F5 ; do the previous and current reads of JOYPAD1 match? 127 | BNE @remember ; if they don't match, read again 128 | CPY $F6 ; do the previous and current reads of JOYPAD2 match? 129 | BNE @remember ; if they don't match, read again 130 | JSR OrPads ; OR the built-in and expansion pads together 131 | VerifyDownPads: 132 | JSR DetectUpToDownTransitions 133 | RTS 134 | 135 | ; Read both hard-wired Famicom and expansion port joypad, but store their data 136 | ; separately instead of ORing them together like the other routines do. This 137 | ; routine is NOT DMC fortified. 138 | ; Returns: $f1-$f4 = joypad data, $f5-$f8 = up-to-down transitions in the order : Pad1, Pad2, Expansion1, Expansion2 139 | ; Affects: A, X, Y, $00, $01, $F1, $F2, $F3, $F4, $F5, $F6, $F7, $F8 140 | API_ENTRYPOINT $ea68 141 | ReadDownExpPads: 142 | JSR ReadPads 143 | ; exp1 and exp2 are in $00 and $01, so copy them to their destinations 144 | LDA $00 145 | STA $F7 146 | LDA $01 147 | STA $F8 148 | LDX #3 ; handle expansion pad #2 first 149 | @DetectUpToDownOnePadExp: 150 | LDA $F5,X ; load current joypad state 151 | TAY ; preserve the current state in Y 152 | EOR $F1,X ; exclusive-OR between previous and current state says which buttons have changed 153 | AND $F5,X ; AND of that with current state says which buttons have changed to down 154 | STA $F5,X ; save up-down transitions 155 | STY $F1,X ; overwrite previous state with current state via temporary 156 | DEX ; handle pad #1 next (offset 0), otherwise return 157 | BPL @DetectUpToDownOnePadExp ; when X underflows to -127, this branch will not be taken 158 | RTS 159 | -------------------------------------------------------------------------------- /gethardcodedpointers.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; This routine does 3 things. First, it fetches 1 or 2 hardcoded 16-bit 17 | ; pointers that follow the second return address. Second, it checks the 18 | ; disk set or even write-protect status of the disk, and if the checks fail, 19 | ; the first return address on the stack is discarded, and program control is 20 | ; returned to the second return address. Finally, it saves the position of 21 | ; the stack so that when an error occurs, program control will be returned to 22 | ; the same place. 23 | ; Parameters: A == -1 means there are two 16-bit pointer parameters, otherwise just 1. 24 | ; Returns: A == OK (0) if no error, or DISK_NOT_SET (1) if the disk is not set. ($00) contains where results were loaded 25 | ; Affects: 26 | ;params 27 | ;------ 28 | ;2nd call addr 1 or 2 16-bit pointers 29 | 30 | ;A -1 2 16-bit pointers are present 31 | ; other values 1 16-bit pointer present 32 | 33 | 34 | ;rtns (no error) 35 | ;--------------- 36 | ;PC original call address 37 | 38 | ;A 00 39 | 40 | ;[$00] where parameters were loaded (A is placed in [$02] if not -1) 41 | 42 | 43 | ;(error) 44 | ;------- 45 | ;PC second call address 46 | 47 | ;Y byte stored in [$0E] 48 | 49 | ;A 01 if disk wasn't set 50 | ; 03 if disk is write-protected 51 | 52 | API_ENTRYPOINT $e3e7 53 | GetHardCodedPointers: 54 | CLC 55 | BCC GetHardCodedPointersImpl 56 | 57 | ; Same as GetHardCodedPointers, except that 58 | ; Returns: A == OK (0) if no error, DISK_NOT_SET (1) if disk is not set, and WRITE_PROTECTED (3) if the disk is write-protected. 59 | API_ENTRYPOINT $e3ea 60 | GetHardCodedPointersWriteProtected: 61 | SEC 62 | GetHardCodedPointersImpl: 63 | 64 | BCC @end ; skip write-protect check if C is false 65 | LDA DRIVESTATUS 66 | AND #%00000100 ; bit 2 contains the write-protect status 67 | BEQ @end 68 | 69 | ; error handling 70 | @end: 71 | RTS 72 | -------------------------------------------------------------------------------- /getvrambufferbyte.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; This routine was likely planned to be used in order to avoid useless latency 17 | ; on VRAM reads. It compares the VRAM address in ($00) with the address in the 18 | ; Yth slot (minimum 1) of the read buffer. If both addresses match, the 19 | ; corresponding data byte is returned in A with the carry flag clear. If the 20 | ; addresses are different, the address in the Yth slot is overwritten with the 21 | ; address in ($00), and the carry flag is set on return. 22 | ; Parameters: X = starting index of read buffer, Y = read buffer slot # (minimum 1), $00, $01 = VRAM address to compare with address in read buffer slot 23 | ; Returns: carry clear : a previously read byte was returned, carry set : no byte was read, should wait next call to ReadVRAMBuffer 24 | ; Affects: A, X, Y 25 | API_ENTRYPOINT $e94f 26 | GetVRAMBufferByte: 27 | DEX 28 | DEX 29 | DEX 30 | TXA 31 | @loop: 32 | CLC 33 | ADC #3 34 | DEY 35 | BNE @loop 36 | TAX 37 | LDA $00 38 | CMP $300,X 39 | BNE @UpdateAddressHi 40 | LDA $01 41 | CMP $301,X 42 | BNE @UpdateAddressLo 43 | LDA $302,X 44 | CLC 45 | RTS 46 | 47 | @UpdateAddressHi: 48 | STA $300,X 49 | LDA $01 50 | @UpdateAddressLo: 51 | STA $301,X 52 | SEC 53 | RTS 54 | -------------------------------------------------------------------------------- /irq.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Bits 6 and 7 of IRQ_ACTION determine the behavior of IRQs. 17 | ; ($DFFE): disk game IRQ vector (if [$0101] = %11xxxxxx) 18 | ; $E1EF : BIOS acknowledge and delay (if [$0101] = %10xxxxxx) 19 | ; $E1CE : BIOS disk transfer (if [$0101] = %01xxxxxx) 20 | ; $E1D9 : BIOS disk skip bytes (if [$0101] = %00nnnnnn) 21 | ; Set to $80 on reset, aka BIOS acknowledge and delay. 22 | IRQ_ACTION EQU $0101 23 | IRQ_VEC EQU $DFFE 24 | 25 | IRQ: 26 | BIT IRQ_ACTION 27 | BMI @actions2and3 28 | BVS @diskXfer 29 | 30 | ; BIOS disk skip bytes - skip n+1 bytes, where n is the lower 6 bits of $101. 31 | ; Each IRQ reduces the number of bytes to skip by 1. 32 | PHA 33 | LDA IRQ_ACTION 34 | SEC 35 | SBC #1 ; another byte skipped, so decrement the bytes remaining to skip 36 | BCC @byteskipped 37 | ; once the subtraction underflows, n+1 bytes have been skipped. Set the 38 | ; IRQ action back to the disk's ISR and clear the byte transfer flag. 39 | STA IRQ_ACTION 40 | LDA READDATA ; clear the byte transfer flag in $4030 41 | @byteskipped: 42 | PLA ; restore A 43 | RTI 44 | 45 | ; BIOS disk transfer - assumes that the IRQ was enabled by one of the disk 46 | ; routines attempting to transfer data. It doesn't know whether read or write 47 | ; is desired, so it does both - it writes to the output register and reads 48 | ; from the input register. It also manipulates the stack to behave like a 49 | ; subroutine call from the caller of Xfer1stByte or XferByte. 50 | @diskXfer: 51 | STA WRITEDATA 52 | PLA ; pop processor status off the stack 53 | PLA ; discard the ISR return address 54 | PLA ; so we can return to the caller of Xfer1stByte or XferByte 55 | LDA READDATA 56 | RTS 57 | 58 | @actions2and3: 59 | BVS @gameIRQ 60 | 61 | ; BIOS acknowledge and delay 62 | PHA 63 | LDA DISKSTATUS ; clear byte transfer flag 64 | JSR Delay131 65 | PLA 66 | RTI 67 | 68 | @gameIRQ: 69 | JMP (IRQ_VEC) ; game's IRQ vector 70 | -------------------------------------------------------------------------------- /loadtileset.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; $eb66 is the beginning of the LoadTileset section of the ROM, but the 17 | ; entrypoint isn't until $ebaf 18 | API_ENTRYPOINT $eb66 19 | AdvanceTileBy8: 20 | LDA #8 21 | AdvanceTileByA: 22 | LDY #0 ; reset the buffer index 23 | PHP ; we need to preserve the carry bit - it has the transfer direction 24 | CLC 25 | ADC $00 26 | STA $00 27 | LDA #0 28 | ADC $01 29 | STA $01 30 | PLP ; restore the carry bit 31 | DEC $03 32 | RTS 33 | 34 | Fill8: 35 | LDX #8 36 | LDA $04 37 | @loop: 38 | BCC @write8 39 | LDA PPUDATA ; advance PPUADDR 1 byte 40 | BCS @next 41 | @write8: 42 | STA PPUDATA 43 | @next: 44 | DEX 45 | BNE @loop 46 | RTS 47 | 48 | Copy8: 49 | LDX #8 50 | @loop: 51 | BCC @write8 52 | LDA PPUDATA 53 | STA ($00),Y 54 | BCS @next 55 | @write8: 56 | LDA ($00),Y 57 | STA PPUDATA 58 | @next: 59 | INY 60 | DEX 61 | BNE @loop 62 | RTS 63 | 64 | Write8XOR: 65 | LDX #8 66 | @loop: 67 | LDA ($00),Y 68 | EOR $04 69 | STA PPUDATA 70 | INY 71 | DEX 72 | BNE @loop 73 | RTS 74 | 75 | ; This routine can read and write 2BP and 1BP tilesets to/from VRAM. 76 | ; The flags parameters are as follows: 77 | ; 78 | ; 7 bit 0 79 | ; --------- 80 | ; AAAA MMIT 81 | ; |||| |||| 82 | ; |||| |||+- Fill bit 83 | ; |||| ||+-- Transfer direction (0 = Write tiles, 1 = Read tiles) 84 | ; |||| ++--- Bitplane type (see below) 85 | ; ++++------ Low VRAM Address (aka tile # within a row) 86 | ; 87 | ; 1st bitplane 2nd bitplane Description 88 | ; ----------- ----------- ----------- 89 | ; 0: data data+8 Normal 2-bitplane graphics 90 | ; 1: data fill bit Single bitplane graphics. Fill bit clear : Use colors 0&1 Fill bit set : Use colors 2&3 91 | ; 2: fill bit data Single bitplane graphics. Fill bit clear : Use colors 0&2 Fill bit set : Use colors 1&3 92 | ; 3: data^fill bit data Single bitplane graphics. Fill bit clear : Use colors 0&3 Fill bit set : Use colors 1&2 93 | ; This makes it possible for single bitplane tiles to take all possible color 94 | ; schemes when they end up in VRAM. However, it is not possible to (natively) 95 | ; load single bitplane graphics directly from the disk into VRAM; it should be 96 | ; loaded into PRG-RAM before transferring the data into VRAM. In read mode, all 97 | ; non "data" bitplanes are replaced by dummy reads. Also, mode 3 ONLY works in 98 | ; write mode! 99 | ; Parameters: A = Low VRAM Address & Flags, Y = Hi VRAM Address, X = # of tiles to transfer to/from VRAM, Direct Pointer = CPU address 100 | ; Affects: A, X, Y, $00, $01, $02, $03, $04, $05, $06 101 | API_ENTRYPOINT $ebaf 102 | LoadTileset: 103 | TOP PPUSTATUS ; clear PPU address latch 104 | STY PPUADDR ; set high byte of PPU address 105 | STA $02 ; store flags until we need them 106 | AND #$F0 107 | STA PPUADDR ; set low byte of VRAM address 108 | STX $03 ; store length 109 | JSR FetchDirectPtr 110 | 111 | LDA ZP_PPUCTRL 112 | AND #%11111011 ; set increment mode to 1 113 | STA ZP_PPUCTRL 114 | STA PPUCTRL 115 | 116 | LDA #0 117 | TAY ; Y = 0, start at beginning of buffer 118 | LSR $02 119 | BCC @fillClear 120 | LDA #$FF 121 | @fillClear: 122 | STA $04 ; save fill byte 123 | 124 | LSR $02 ; shift the transfer direction into the carry flag and keep it there 125 | BCC @writeMode 126 | LDA PPUDATA ; read and throw away a byte, to prime the PPU read buffer 127 | @writeMode: 128 | LDA #%00000011 ; mask the lowest two bits, where the mode has been shifted to 129 | AND $02 130 | BEQ @mode0 131 | CMP #2 132 | BEQ @mode2 ; if (mode == 2) 133 | BCC @mode1 ; if (mode < 2) 134 | 135 | @mode3: 136 | JSR Write8XOR 137 | LDY #0 ; Read the same data 138 | JSR Copy8 139 | JSR AdvanceTileBy8 140 | BNE @mode3 141 | RTS 142 | 143 | @mode0: 144 | JSR Copy8 145 | JSR Copy8 146 | LDA #16 147 | JSR AdvanceTileByA 148 | BNE @mode0 149 | RTS 150 | 151 | @mode1: 152 | JSR Copy8 153 | JSR Fill8 154 | JSR AdvanceTileBy8 155 | BNE @mode1 156 | RTS 157 | 158 | @mode2: 159 | JSR Fill8 160 | JSR Copy8 161 | JSR AdvanceTileBy8 162 | BNE @mode2 163 | RTS 164 | -------------------------------------------------------------------------------- /nametable.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Convert pixel screen coordinates to corresponding nametable address (assumes 17 | ; no scrolling, and points to first nametable at $2000-$23ff). 18 | ; Parameters: $03 = Pixel X cord, $02 = Pixel Y cord 19 | ; Returns: $00 = High nametable address, $01 = Low nametable address 20 | ; Affects: A 21 | API_ENTRYPOINT $e97d 22 | Pixel2NamConv: 23 | ; right shift X 3 times to divide by 8 (each NT byte represents an 8x8 area) 24 | LDA $03 25 | LSR A 26 | LSR A 27 | LSR A 28 | STA $01 29 | 30 | ; Set the high part of the address to $20 but shifted right twice, so that 31 | ; it will be shifted left twice later 32 | LDA #$08 33 | STA $00 34 | 35 | ; Every 8 pixels of Y represents 32 bytes of NT, so instead of dividing by 36 | ; 8 and then multiplying by 32, just multiply by 4 and mask the rest 37 | LDA $02 38 | ASL A 39 | ROL $00 40 | ASL A 41 | ROL $00 42 | AND #%11100000 43 | ; carry is guaranteed to be clear - $00 was 8 before ROL touched it 44 | ADC $01 45 | STA $01 46 | 47 | RTS 48 | 49 | ; Convert a nametable address to corresponding pixel coordinates (assume no 50 | ; scrolling). 51 | ; Parameters: $00 = High nametable address, $01 = low nametable address 52 | ; Returns: $03 = Pixel X cord, $02 = Pixel Y cord 53 | ; Affects: A 54 | API_ENTRYPOINT $e997 55 | Nam2PixelConv: 56 | LDA $01 57 | ; three left shifts doesn't just multiply by 8, it also shifts out the Y 58 | ; coordinate portion of the address 59 | ASL A 60 | ASL A 61 | ASL A 62 | STA $03 63 | 64 | ; the top three bits of the low address are part of the Y coordinate 65 | LDA $01 66 | AND #%11100000 67 | STA $02 68 | LDA $00 69 | ; The two lowest bits of the high address are the two high bits of the Y 70 | ; coordinate. Shift right to get those bits into the carry flag, and 71 | ; rotate the carry flag into the Y coordinate 72 | LSR A 73 | ROR $02 74 | LSR A 75 | ROR $02 76 | 77 | RTS 78 | -------------------------------------------------------------------------------- /nmi.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Bits 6 and 7 of NMI_ACTION choose which of the disk's 3 NMI vectors to use, 17 | ; or '0' to indicate the use of VINTWait. The three vectors are stored in 18 | ; order starting at $DFF6. Set to $C0 on reset, aka NMI vector 3. 19 | NMI_ACTION EQU $0100 20 | NMI_VEC1 EQU $DFF6 21 | NMI_VEC2 EQU $DFF8 22 | NMI_VEC3 EQU $DFFA 23 | 24 | ; The BIOS NMI provides programs the choice of up to 3 user-selectable NMI 25 | ; vectors, or using VINTWait (the "everything in main" way.) If VINTWait 26 | ; was used, then the BIOS NMI does the following: 27 | ; * Clears the VBlank flag in $2002. 28 | ; * Disables future NMIs, assuming that the program will just call VINTWait 29 | ; again. 30 | ; * Manipulates the stack to make the NMI behave as a subroutine call instead 31 | ; of an ISR. 32 | ; * Pulls the NMI vector selection and accumulator from the stack, then RTS 33 | ; to return to the caller of VINTWait. 34 | ; Affects: A 35 | API_ENTRYPOINT $e18b 36 | NMI: 37 | BIT NMI_ACTION 38 | BMI @vecs2and3 39 | BVS @vec1 40 | 41 | ; value was 0, assuming VINTWait was used 42 | LDA PPUSTATUS ; clear the VBlank flag 43 | LDA ZP_PPUCTRL 44 | AND #%01111111 ; clear NMI enable flag 45 | STA PPUCTRL 46 | STA ZP_PPUCTRL 47 | PLA ; discard the saved processor flags 48 | PLA ; discard the return address pushed onto the stack by the interrupt 49 | PLA ; (presumably it pointed to the infinite loop in VINTWait) 50 | PLA ; restore NMI_ACTION 51 | STA NMI_ACTION 52 | PLA ; restore VINTWait's caller's accumulator 53 | RTS ; return to VINTWait's caller 54 | 55 | @vec1: 56 | JMP (NMI_VEC1) 57 | @vecs2and3: 58 | BVS @vec3 59 | JMP (NMI_VEC2) 60 | @vec3: 61 | JMP (NMI_VEC3) 62 | 63 | ; Spin until next VBlank NMI fires, for programs that do it the "everything in 64 | ; main" way. The accumulator and the NMI vector selection at $100 are saved to 65 | ; the stack, and further VBlanks are disabled. 66 | ; Affects: $FF 67 | API_ENTRYPOINT $e1b2 68 | VINTWait: 69 | PHA ; save current A 70 | LDA NMI_ACTION ; get current NMI selection 71 | PHA ; and save it on the stack 72 | LDA #0 73 | STA NMI_ACTION ; tell the BIOS to disable NMIs 74 | LDA ZP_PPUCTRL ; but make sure the next NMI happens 75 | ORA #%10000000 ; so the spinning stops when the NMI fires 76 | STA PPUCTRL 77 | STA ZP_PPUCTRL 78 | @spin: 79 | BNE @spin ; now nothing left to do but wait 80 | -------------------------------------------------------------------------------- /ppumask.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Disable sprites and playfield, affects A, $FE 17 | API_ENTRYPOINT $e161 18 | DisPFObj: 19 | LDA ZP_PPUMASK ; Get the existing value 20 | AND #%11100111 ; clear bits 3 and 4 21 | WritePPUMask: 22 | STA ZP_PPUMASK ; Write it to RAM 23 | STA PPUMASK ; Write it to PPUMASK 24 | RTS 25 | 26 | ; Enable sprites and playfield, affects A, $FE 27 | API_ENTRYPOINT $e16b 28 | EnPFObj: 29 | LDA ZP_PPUMASK ; Get the existing value 30 | ORA #%00011000 ; set bits 3 and 4 31 | ; re-use DisPFObj's implementation of writing the registers to save bytes 32 | ; only six bytes allowed in this function, and JMP would make it 7. 33 | ; use BNE to jump backwards because Z is guaranteed to be 0 (from the 34 | ; non-zero ORA immediate) and that's only 2 bytes 35 | BNE WritePPUMask 36 | 37 | ; Disable sprites, affects A, $FE 38 | API_ENTRYPOINT $e171 39 | DisObj: 40 | LDA ZP_PPUMASK ; Get the existing value 41 | AND #%11101111 ; clear bit 4 42 | ; Can't trust BNE like EnPFObj, because the result of the preceding AND 43 | ; could be 0. However, we have 7 bytes to play with, so JMP works fine. 44 | JMP WritePPUMask 45 | 46 | ; Enable sprites, affects A, $FE 47 | API_ENTRYPOINT $e178 48 | EnObj: 49 | LDA ZP_PPUMASK ; Get the existing value 50 | ORA #%00010000 ; set bit 4 51 | ; Branch to DisPFObj's implementation of writing the result to save bytes 52 | BNE WritePPUMask 53 | 54 | ; Disable playfield, affects A, $FE 55 | API_ENTRYPOINT $e17e 56 | DisPF: 57 | LDA ZP_PPUMASK ; Get the existing value 58 | AND #%11110111 ; clear bit 3 59 | ; Can't trust BNE like EnPFObj, because the result of the preceding AND 60 | ; could be 0. However, we have 7 bytes to play with, so JMP works fine. 61 | JMP WritePPUMask 62 | 63 | ; Enable playfield, affects A, $FE 64 | API_ENTRYPOINT $e185 65 | EnPF: 66 | LDA ZP_PPUMASK ; Get the existing value 67 | ORA #%00001000 ; set bit 3 68 | ; Branch to DisPFObj's implementation of writing the result to save bytes 69 | BNE WritePPUMask 70 | -------------------------------------------------------------------------------- /random.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Shift-register based random number generator, normally takes 2 bytes (using 17 | ; more won't affect random sequence). On reset the program is supposed to 18 | ; write some non-zero values here (BIOS uses writes $d0, $d0), and call this 19 | ; routine several times before the data is actually random. Each call of this 20 | ; routine will shift the bytes right. 21 | ; 22 | ; Algorithm: 23 | ; 1. exclusive-OR the bit 1s of [X] and [X+1]. 24 | ; 2. Rotate [X] to the right, rotating a 1 into the MSB if step 1 was non-zero. 25 | ; 3. Rotate each of the remaining bytes [X+1] ... [X+(Y-1)] to the right, 26 | ; carrying the LSB of the previous byte into the MSB of the next. 27 | ; 28 | ; Parameters: X = Zero Page address where the random bytes are placed 29 | ; Y = # of shift register bytes (normally 2) 30 | ; Affects: A, X, Y, $00 31 | API_ENTRYPOINT $e9b1 32 | Random: 33 | LDA $00,X 34 | AND #$02 35 | STA $00 36 | LDA $01,X 37 | AND #$02 38 | EOR $00 39 | ; rotate a 1 into the first byte if the XOR was non-zero 40 | SEC 41 | BNE @shifter 42 | ; it was 0, so rotate a 0 into the first byte 43 | CLC 44 | @shifter: 45 | ROR $00,X 46 | INX 47 | DEY 48 | BNE @shifter 49 | RTS 50 | -------------------------------------------------------------------------------- /reset.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ;[$0102]/[$0103]: PC action on reset 17 | ;($DFFC): disk game reset vector (if [$0102] = $35, and [$0103] = $53 or $AC) 18 | RESET_ACTION_1 EQU $0102 19 | RESET_ACTION_2 EQU $0103 20 | DISK_RESET_VEC EQU $DFFC 21 | 22 | 23 | RESET: 24 | SEI ; disable interrupts 25 | CLD ; clear decimal mode flag, which doesn't work on the 2A03 anyway 26 | 27 | ; don't allow NMIs until we're ready 28 | LDY #$00 29 | STY ZP_PPUCTRL 30 | STY PPUCTRL 31 | 32 | ; disable rendering, but enable left 8 pixels 33 | LDA #$06 34 | STA ZP_PPUMASK 35 | STA PPUMASK 36 | 37 | ; disable disk I/O and IRQs 38 | STY IRQCTRL 39 | STY MASTERIO 40 | 41 | ; the PPU takes a while to warm up. Wait for at least 2 VBlanks before 42 | ; trying to set the scroll registers 43 | LDX #2 44 | @waitForVBL: 45 | LDA PPUSTATUS 46 | BPL @waitForVBL 47 | DEX 48 | BNE @waitForVBL 49 | 50 | ; clear the scroll registers 51 | LDA #0 52 | STA ZP_PPUSCROLL1 53 | STA PPUSCROLL 54 | STA ZP_PPUSCROLL2 55 | STA PPUSCROLL 56 | 57 | ; nothing has been written to the joypads or expansion port 58 | STA ZP_JOYPAD1 59 | 60 | LDA #$2E 61 | STA ZP_FDSCTRL 62 | STA FDSCTRL 63 | 64 | LDA #$FF 65 | STA ZP_EXTCONN 66 | 67 | ; ready, now we can turn on NMIs 68 | LDA #$80 69 | STA ZP_PPUCTRL 70 | STA PPUCTRL 71 | 72 | ; prepare the VRAM buffer 73 | LDA #$7D ; initial write buffer found at $302-$37F 74 | STA $300 75 | LDA #0 76 | STA $301 77 | LDA #$80 ; "end" opcode for the write buffer 78 | STA $302 79 | -------------------------------------------------------------------------------- /vrambuffers.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Write buffers from the VRAM Write Buffer, starting at $302, to VRAM. Each 17 | ; buffer is described and preceded by a 3 byte header. 18 | ; The first two bytes specify the destination VRAM address. 19 | ; - To mark the end of the list of buffers, put a value >= $80 instead in the 20 | ; first byte. 21 | ; - The original version checks if each structure's address STARTS in the 22 | ; palette (addr >= $3F00), then it sets PPUADDR to $3F00 followed by $0000 23 | ; after copying that structure. It appears to be an attempt to prevent the 24 | ; PPU from drawing the colors of the palette to the screen, AKA, the 25 | ; "background palette hack," which occurs whenever rendering is off and the 26 | ; PPUADDR is somewhere in the palette. (FWIW, rendering is likely to be off 27 | ; when this function is called.) 28 | ; To reliably prevent the palette from being drawn to the screen, what this 29 | ; implementation does instead is to check whether the address of just the 30 | ; last structure PLUS its length is in the $3F00-$3FFF area (or the mirror at 31 | ; $7F00-$7FFF). 32 | ; The third byte contains the length of the buffer. 33 | ; - There's no check for 0-length buffers. Specifying a length of 0 will cause 34 | ; a buffer overflow. 35 | ; Before returning, the write buffer is cleared (0 written to $301, $80 written 36 | ; to $302.) 37 | ; Affects: A, X, Y, $02, $03, $04, $301, $302 38 | API_ENTRYPOINT $e86a 39 | WriteVRAMBuffers: 40 | LDA ZP_PPUCTRL 41 | AND #%11111011 ; Address increment of 1 42 | STA PPUCTRL 43 | STA ZP_PPUCTRL 44 | LDA PPUSTATUS ; clear PPUADDR latch 45 | LDY #0 ; Start at the beginning of $302 46 | @structure: 47 | LDA $302,Y ; load high byte of destination PPU Address 48 | BMI @done ; an "opcode" of $80 or more marks the end of the list 49 | STA $03 ; save the address (little endian) so we can check later for PPUADDR in the palette 50 | STA PPUADDR ; set the high byte of the initial address 51 | INY 52 | LDA $302,Y ; load low byte of destination PPU address 53 | STA $02 ; save the low byte of the address (little endian) 54 | STA PPUADDR ; set the low byte of the initial address 55 | INY 56 | LDX $302,Y ; load buffer length from third byte 57 | STX $04 ; save the length so we can check later for PPUADDR in the palette 58 | INY 59 | @loop: 60 | LDA $302,Y 61 | STA PPUDATA 62 | INY 63 | DEX 64 | BNE @loop 65 | BEQ @structure ; this structure is done, so move onto the next one 66 | @done: 67 | STA $302 ; write the $80 "opcode" to the first structure's address to clear the list 68 | LDA #0 ; clear the write buffer, i.e., move the read buffer to the beginning 69 | STA $301 70 | ; Finally, clear PPUADDR if it's currently in the palette 71 | JSR PreventPalettePpuAddr 72 | RTS 73 | 74 | ; Read individual bytes from VRAM to the VRAMBuffer. Each byte in the buffer is 75 | ; preceded by the source address - (1|32) in VRAM; in other words, it takes 3 76 | ; bytes of space in the VRAM Buffer for every byte to read, and the byte 77 | ; written to the buffer comes from 1 byte or 32 bytes LATER than the specified 78 | ; address, as this function does NOT change the address increment mode. 79 | ; Affects A, X, Y 80 | ; Parameters: X = start address of read buffer, Y = # of bytes to read 81 | API_ENTRYPOINT $e8b3 82 | ReadIndividualVRAMBytes: 83 | LDA PPUSTATUS ; clear PPUADDR latch 84 | @loop: 85 | LDA $300,X ; high address byte 86 | STA PPUADDR 87 | INX 88 | LDA $300,X ; load low address byte 89 | STA PPUADDR 90 | INX 91 | LDA PPUDATA ; read (and throw away) buffered byte from VRAM 92 | LDA PPUDATA ; read buffered byte from VRAM 93 | STA $300,X 94 | INX 95 | DEY 96 | BNE @loop 97 | RTS 98 | -------------------------------------------------------------------------------- /vramfill.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; memset for VRAM. 17 | ; If A < $20, it fills pattern table data with the value in X for 16 * Y tiles, 18 | ; or Y * 256 bytes. 19 | ; If A >= $20, it fills the corresponding nametable with the value in X and 20 | ; attribute table with the value in Y. 21 | ; Parameters: A = High VRAM Address (aka tile row #), X = Fill value, Y = # of tile rows OR attribute fill data 22 | ; Affects: A, $00, $01, $02, $FF (aka PPUCTRL) 23 | API_ENTRYPOINT $ea84 24 | VRAMFill: 25 | STY $02 ; preserve Y so it can be restored later 26 | ; every write to PPUADDR needs to ensure that the PPUADDR latch is clear 27 | ; one nice feature of TOP is that it reads without clobbering anything 28 | TOP PPUSTATUS 29 | ; set the address 30 | STA PPUADDR 31 | LDA #0 32 | STA PPUADDR 33 | ; set the correct VRAM increment mode of 1 34 | LDA ZP_PPUCTRL 35 | AND #%11111011 36 | STA PPUCTRL 37 | STA ZP_PPUCTRL 38 | ; pattern or nametable fill? 39 | CMP #$20 40 | BCS @VramNametableFill 41 | @VramPatternFill: 42 | CLC 43 | ; The address has been loaded, so now we can use A as a counter 44 | ; 16 tiles in a row, 16 bytes per tile = 256 bytes to write per row 45 | LDA #0 46 | @tile: 47 | STX PPUDATA 48 | ADC #1 49 | BCC @tile 50 | DEY 51 | BNE @VramPatternFill 52 | 53 | ; restore Y (X never changed) 54 | LDY $02 55 | RTS 56 | 57 | @VramNametableFill: 58 | ; fill the nametable with X for 960 bytes, or 4 * 240 bytes 59 | STX $01 ; preserve X so it can be restored later 60 | TXA 61 | LDX #4 62 | @quarterNT 63 | LDY #240 64 | @name: 65 | STA PPUDATA 66 | DEY 67 | BNE @name 68 | DEX 69 | BNE @quarterNT 70 | ; restore the attr file value in Y 71 | LDY $02 72 | ; then fill the attributes with Y for 64 bytes 73 | LDX #64 74 | @attr: 75 | STY PPUDATA 76 | DEX 77 | BNE @attr 78 | 79 | ; restore X (Y is already restored) 80 | LDX $01 81 | RTS 82 | -------------------------------------------------------------------------------- /vramstrings.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Copy pointed data into the VRAM buffer. 17 | ; Parameters: A = High VRAM address, X = Low VRAM address, Y = string length, Direct Pointer = data to be written to VRAM 18 | ; Returns: A = $ff : no error, A = $01 : string didn't fit in buffer 19 | ; Affects: A, X, Y, $00, $01, $02, $03, $04, $05, $06 20 | API_ENTRYPOINT $e8d2 21 | PrepareVRAMString: 22 | STA $02 23 | STX $03 24 | STY $05 25 | JSR FetchDirectPtr 26 | LDY #0 27 | LDA #1 28 | BEQ PrepareVRAMStringsGeneric 29 | 30 | ; Copy a 2D string into the VRAM buffer. The first byte of the data determines 31 | ; the width and height of the following string (in tiles): 32 | ; Upper nybble = height, lower nybble = width. 33 | ; Parameters: A = High VRAM address, X = Low VRAM address, Direct pointer = data to be written to VRAM 34 | ; Returns: A = $ff : no error, A = $01 : data didn't fit in buffer 35 | ; Affects: A, X, Y, $00, $01, $02, $03, $04, $05, $06 36 | API_ENTRYPOINT $e8e1 37 | PrepareVRAMStrings: 38 | STA $02 39 | STX $03 40 | JSR FetchDirectPtr 41 | LDY #0 42 | LDA #$0F ; width 43 | AND ($00),Y 44 | STA $05 45 | LDA ($00),Y ; get the first byte of the data, which indicates the width and height 46 | INY ; Y is the read index 47 | LSR ; Shift the upper nibble 4 bits into the lower nibble 48 | LSR 49 | LSR 50 | LSR 51 | ; for (row = $04; row > 0; row--) 52 | ; $04 holds # of rows to go 53 | PrepareVRAMStringsGeneric: 54 | STA $04 ; store the height 55 | LDX $301 ; X is the write index 56 | @buffer: 57 | ; write destination address (BIG ENDIAN) 58 | LDA $02 59 | STA $302,X 60 | JSR IncrementWriteIndex 61 | LDA $03 62 | STA $302,X 63 | JSR IncrementWriteIndex 64 | CLC 65 | ADC #32; the next string should write to the next row 66 | STA $03 67 | BCC @writeLength 68 | INC $02 69 | @writeLength: 70 | LDA $05 71 | STA $302,X 72 | JSR IncrementWriteIndex 73 | STA $06 74 | @writeString: 75 | LDA ($00),Y 76 | INY 77 | STA $302,X 78 | JSR IncrementWriteIndex 79 | DEC $06 80 | BNE @writeString ; reached the end of this string? 81 | DEC $04 82 | BNE @buffer ; reached the end of the strings? 83 | LDA #$FF ; no error 84 | STA $302,X ; write end opcode to write buffer 85 | STX $301 ; update write buffer end index 86 | RTS 87 | 88 | ; Increment the write position in X and check if that would overflow the buffer 89 | IncrementWriteIndex: 90 | INX 91 | CPX $300 92 | BCC @done 93 | ; uh oh, out of space! Pop the return address pointing to 94 | ; PrepareVRAMStrings off the stack so that we return to PrepareVRAMStrings' 95 | ; caller instead. 96 | PLA 97 | PLA 98 | LDX $301 ; since it overflowed, put the "end" opcode back where we started 99 | LDA #$FF 100 | STA $302,X ; write end opcode to write buffer 101 | LDA #1 ; 1 is the error return code 102 | @done: 103 | RTS 104 | -------------------------------------------------------------------------------- /vramstructwrite.asm: -------------------------------------------------------------------------------- 1 | ; FreeDiskSysROM 2 | ; Copyright (c) 2018 James Athey 3 | ; 4 | ; This program is free software: you can redistribute it and/or modify it under 5 | ; the terms of the GNU Lesser General Public License version 3 as published by 6 | ; the Free Software Foundation. 7 | ; 8 | ; This program is distributed in the hope that it will be useful, but WITHOUT 9 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | ; FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | ; details. 12 | ; 13 | ; You should have received a copy of the GNU Lesser General Public License 14 | ; along with this program. If not, see . 15 | 16 | ; Affects: A, X, Y, $00, $01, $02, $03, $04, $FF 17 | ; Parameters: Direct Pointer to VRAM buffer to be written 18 | API_ENTRYPOINT $e7bb 19 | VRAMStructWrite: 20 | LDA PPUSTATUS ; clear PPUADDR latch 21 | JSR FetchDirectPtr ; start of the buffer is now in ($00) 22 | @buffer: 23 | LDY #0 24 | @structure: 25 | LDA ($00),Y ; load high byte of destination PPU Address 26 | BMI @done ; >= $80 is the finish opcode 27 | CMP #$4C ; "call" opcode 28 | BEQ @call 29 | CMP #$60 ; "return" opcode 30 | BEQ @return 31 | STA $03 ; save the high byte of the address (little endian) 32 | STA PPUADDR ; set the high byte of the initial address 33 | INY 34 | 35 | LDA ($00),Y ; load low byte of destination PPU address 36 | STA $02 ; save the low byte of the address (little endian) 37 | STA PPUADDR ; set the low byte of the initial address 38 | INY 39 | 40 | LDX ($00),Y ; load buffer length and flags from third byte 41 | TXA 42 | AND #%00111111 ; if bits 0-5 are all zero (length of 0), then the length is really 64 43 | BNE @savelength 44 | LDA #64 45 | @savelength: 46 | STA $04 ; save the length so we can check later for PPUADDR in the palette 47 | INY 48 | TXA 49 | BMI @incr32 ; if bit 7 set, increment mode is 32 50 | LDA ZP_PPUCTRL 51 | AND #%11111011 ; Address increment of 1 52 | JMP @setincr 53 | @incr32: 54 | LDA ZP_PPUCTRL 55 | ORA #%00000100 ; Address increment of 32 56 | @setincr: 57 | STA PPUCTRL 58 | STA ZP_PPUCTRL 59 | TXA 60 | AND #%01000000 ; check the mode flag 61 | BEQ @copy 62 | ; must be fill then 63 | 64 | @fill: 65 | LDX $04 66 | LDA ($00),Y 67 | INY 68 | @fillloop: 69 | STA PPUDATA 70 | DEX 71 | BNE @fillloop 72 | BEQ @structure ; this structure is done, so move onto the next one 73 | 74 | @copy: 75 | LDX $04 76 | @copyloop: 77 | LDA ($00),Y 78 | STA PPUDATA 79 | INY 80 | DEX 81 | BNE @copyloop 82 | BEQ @structure ; this structure is done, so move onto the next one 83 | 84 | @call: 85 | ; ($00) will be overwritten and Y will be cleared, so they need to be preserved 86 | LDA $01 87 | PHA ; preserve the high address byte on the stack 88 | LDA $00 89 | PHA ; preserve the low address byte on the stack 90 | INY 91 | LDA ($00),Y ; get low address byte of substructure 92 | TAX ; hold onto that low byte 93 | INY 94 | LDA ($00),Y ; get high address byte of substructure 95 | INY 96 | STX $00 ; write the address of the substructure 97 | STA $01 98 | TYA 99 | PHA ; push the Y index onto the stack 100 | BNE @buffer 101 | 102 | @return: 103 | PLA ; restore the Y index 104 | TAY 105 | PLA ; restore the low address byte 106 | STA $00 107 | PLA ; restore the high address byte 108 | STA $01 109 | BNE @structure 110 | 111 | @done: 112 | JSR PreventPalettePpuAddr 113 | RTS 114 | --------------------------------------------------------------------------------