├── README.md └── src ├── APU ├── README.md └── period2midi.js ├── CPU ├── Makefile ├── README.md ├── addressing │ ├── (indirect),Y │ ├── (indirect,X) │ ├── README.md │ ├── absolute │ ├── absolute,X │ ├── absolute,Y │ ├── accumulator │ ├── immidiate │ ├── implied │ ├── indirect │ ├── meta.tosh │ ├── relative │ ├── zeropage │ ├── zeropage,X │ └── zeropage,Y ├── bin │ └── README.md ├── branch-maker.js ├── build-cpu.js ├── build-crement.js ├── build-lookups.js ├── build-reference.sh ├── build-transfer.js ├── build_bitwise.js ├── common.tosh ├── flag-maker.js ├── instructions │ ├── ADC │ ├── ASL │ ├── BIT │ ├── BRK │ ├── CMP │ ├── CPX │ ├── CPY │ ├── JMP │ ├── JSR │ ├── LDA │ ├── LDX │ ├── LDY │ ├── LSR │ ├── NOP │ ├── PHA │ ├── PHP │ ├── PLA │ ├── PLP │ ├── ROL │ ├── ROR │ ├── RTI │ ├── RTS │ ├── SBC │ ├── STA │ ├── STX │ └── STY └── parse-reference.js ├── Input └── input.tosh ├── PPU ├── PPU.tosh ├── README.md ├── build_palette.js └── sRGB.txt ├── ROM ├── ROM.tosh ├── flash.sh └── hexify.sh ├── build-emulator.sh └── tools ├── rip.js └── tableify.js /README.md: -------------------------------------------------------------------------------- 1 | # ScratchNES 2 | _An NES emulator written in MIT Scratch_ 3 | 4 | This is (not so) serious software written for a (not so) serious platform. Therefore, we use git for version control, tosh for high-level programming, and various custom scripts for various different tasks. 5 | -------------------------------------------------------------------------------- /src/APU/README.md: -------------------------------------------------------------------------------- 1 | APU 2 | ========== 3 | 4 | A partial APU is supported. DPCM isn't feasible given the architecture; however, the other channels can be approximated using MIDI note blocks. 5 | -------------------------------------------------------------------------------- /src/APU/period2midi.js: -------------------------------------------------------------------------------- 1 | /* period2midi.js 2 | * generates a table mapping NES periods into MIDI notes 3 | * internally, it's NES period -> frequency (Hz) -> MIDI note 4 | * a lookup table is madef as a result 5 | */ 6 | 7 | var fs = require("fs"); 8 | 9 | /* NTSC CPU clock rate */ 10 | var Hz = 1; 11 | var kHz = 1000 * Hz; 12 | var MHz = kHz * kHz; 13 | var CPU = 1.789773 * MHz; 14 | 15 | var results = []; 16 | 17 | // intentional off-by-one error; 18 | // the idea here is to prevent Scratch from needing to add 1, 19 | // which is OK because nobody's going to really play period 0; 20 | // as a matter of fact, on the real NES periods 0-7 are silent 21 | // Also, this is a %2 table. 22 | 23 | for(var i = 1; i < 2048; i += 2) { 24 | results.push(mapping(i)); 25 | } 26 | 27 | console.log(results.join("\n")); 28 | 29 | function mapping(period) { 30 | var frequency = CPU / ((16*period) + 1); 31 | var midi = 59 + (12*Math.log(frequency / 440)/Math.log(2)); 32 | return Math.round(midi); 33 | } 34 | -------------------------------------------------------------------------------- /src/CPU/Makefile: -------------------------------------------------------------------------------- 1 | bin/reference.txt: build-reference.sh 2 | ./build-reference.sh 3 | 4 | bin/table.json: bin/reference.txt parse-reference.js 5 | node parse-reference.js bin/reference.txt 6 | 7 | bin/EOR.txt: 8 | node build-lookups.js 9 | 10 | instructions/BCC: 11 | node branch-maker.js 12 | 13 | instructions/CLC: 14 | node flag-maker.js 15 | 16 | instructions/INC: 17 | node build-crement.js 18 | 19 | instructions/TAX: 20 | node build-transfer.js 21 | 22 | instructions/EOR: 23 | node build_bitwise.js 24 | 25 | clean: 26 | -cd instructions ; rm BCC BCS BNE BEQ BVC BVS BPL BMI 27 | -cd instructions ; rm CLC SEC CLD SED CLI SEI CLV 28 | -cd instructions ; rm INC INX INY DEC DEX DEY 29 | -cd instructions ; rm TAX TAY TSX TXA TXS TYA 30 | -cd instructions ; rm AND EOR ORA 31 | -------------------------------------------------------------------------------- /src/CPU/README.md: -------------------------------------------------------------------------------- 1 | # CPU Architecture 2 | 3 | The CPU code was automatically generated from the scripts in this directory and the 6502 instruction set reference hosted by e-tradition. 4 | 5 | # Instruction set 6 | 7 | Each possible instruction is stored in a file in instructions/. The first line of the file is a comma-separated list of options. The rest of the file is tosh source code for that instruction in the general case. 8 | 9 | ## Options 10 | 11 | * R 12 | Dereference the address into OP. 13 | 14 | * RW 15 | Load the address into OP before the code runs. After the code runs, copy it back. 16 | 17 | * IMPLIED 18 | Don't do anything. I needed to save a line, k? 19 | 20 | * RAW 21 | Load the effective address into OP, but do not dereference it. 22 | 23 | * N, Z 24 | Automatically update the respective flag in the typical way. 25 | 26 | # Internal format 27 | 28 | Memory is stored as 8-bit unprefixed hex values (e.g.: "FF"). The SR register is implied and broken up into 8 seperate boolean registers. The remaining registers are decimal. 29 | 30 | Please take caution with lookup tables -- they're one-indexed not zero! 31 | 32 | Mapper read/write take addresses in decimal to save on conversion in addressing modes. 33 | -------------------------------------------------------------------------------- /src/CPU/addressing/(indirect),Y: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set temp to M 3 | mapper read (temp) 4 | set address to M 5 | mapper read (temp+1) 6 | set address to address + (256*M) + Y + flagC 7 | -------------------------------------------------------------------------------- /src/CPU/addressing/(indirect,X): -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set temp to M 3 | mapper read (temp + X) mod 256 4 | set tmp to M 5 | mapper read (temp + X + 1) mod 256 6 | set address to (M*256) + tmp 7 | -------------------------------------------------------------------------------- /src/CPU/addressing/README.md: -------------------------------------------------------------------------------- 1 | # 6502 Addressing Modes 2 | 3 | This directory contains stubs for the various 6502 addressing modes. 4 | -------------------------------------------------------------------------------- /src/CPU/addressing/absolute: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set tmp to M 3 | mapper read PC+2 4 | set address to M * 256 + tmp 5 | -------------------------------------------------------------------------------- /src/CPU/addressing/absolute,X: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set tmp to M 3 | mapper read PC+2 4 | set address to (M * 256) + tmp + X 5 | -------------------------------------------------------------------------------- /src/CPU/addressing/absolute,Y: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set tmp to M 3 | mapper read PC+2 4 | set address to M * 256 + tmp + Y 5 | -------------------------------------------------------------------------------- /src/CPU/addressing/accumulator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alyssarosenzweig/ScratchNES/ef3e1725490f23e36de23a425c49e23f11466cd9/src/CPU/addressing/accumulator -------------------------------------------------------------------------------- /src/CPU/addressing/immidiate: -------------------------------------------------------------------------------- 1 | set address to PC+1 2 | -------------------------------------------------------------------------------- /src/CPU/addressing/implied: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alyssarosenzweig/ScratchNES/ef3e1725490f23e36de23a425c49e23f11466cd9/src/CPU/addressing/implied -------------------------------------------------------------------------------- /src/CPU/addressing/indirect: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set tmp to M 3 | mapper read PC+2 4 | mapper read M * 256 + tmp 5 | set tmp2 to M 6 | mapper read M * 256 + tmp + 1 7 | set address to M * 256 + tmp2 8 | -------------------------------------------------------------------------------- /src/CPU/addressing/meta.tosh: -------------------------------------------------------------------------------- 1 | define mapper read (location) 2 | set M to 255 3 | 4 | define immediate 5 | mapper read PC+1 6 | 7 | define zeropage 8 | mapper read PC+1 9 | mapper read M 10 | 11 | define zeropageX 12 | mapper read PC+1 13 | mapper read M + X mod 256 14 | 15 | define zeropageY 16 | mapper read PC+1 17 | mapper read M + Y mod 256 18 | 19 | define absolute 20 | mapper read PC+1 21 | set tmp to M 22 | mapper read PC+2 23 | mapper read tmp * 256 + M 24 | 25 | define absoluteX 26 | mapper read PC+1 27 | set tmp to M 28 | mapper read PC+2 29 | mapper read tmp * 256 + M + X 30 | 31 | define absoluteY 32 | mapper read PC+1 33 | set tmp to M 34 | mapper read PC+2 35 | mapper read tmp * 256 + M + Y 36 | 37 | define implied 38 | 39 | define indirect 40 | mapper read PC+1 41 | set tmp to M 42 | mapper read PC+2 43 | mapper read tmp * 256 + M 44 | set tmp2 to M 45 | mapper read tmp * 256 + M + 1 46 | mapper read tmp2 * 256 + M 47 | 48 | define indirectX 49 | mapper read PC+1 50 | mapper read (M + X) mod 256 51 | 52 | define indirectY 53 | mapper read PC+1 54 | mapper read M 55 | mapper read M + Y + flagC 56 | -------------------------------------------------------------------------------- /src/CPU/addressing/relative: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alyssarosenzweig/ScratchNES/ef3e1725490f23e36de23a425c49e23f11466cd9/src/CPU/addressing/relative -------------------------------------------------------------------------------- /src/CPU/addressing/zeropage: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set address to M 3 | -------------------------------------------------------------------------------- /src/CPU/addressing/zeropage,X: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | set address to (M + X) mod 256 3 | -------------------------------------------------------------------------------- /src/CPU/addressing/zeropage,Y: -------------------------------------------------------------------------------- 1 | mapper read PC+1 2 | mapper read (M + Y) mod 256 3 | -------------------------------------------------------------------------------- /src/CPU/bin/README.md: -------------------------------------------------------------------------------- 1 | This directory contains misc output from the CPU generator. This directory can safely be erased after the CPU is built. 2 | -------------------------------------------------------------------------------- /src/CPU/branch-maker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * branch-maker.js 3 | * look at the name 4 | * it makes branches :P 5 | */ 6 | 7 | var fs = require("fs"); 8 | 9 | // flag: [clear, set] 10 | 11 | var branches = { 12 | "C": ["BCC", "BCS"], 13 | "Z": ["BNE", "BEQ"], 14 | "V": ["BVC", "BVS"], 15 | "N": ["BPL", "BMI"] 16 | }; 17 | 18 | function emit(name, flag, value) { 19 | var emission = []; 20 | 21 | // header 22 | emission.push("BRANCH"); 23 | emission.push("mapper read PC+1"); 24 | 25 | // emission 26 | var flagNum = value ? "flag" + flag 27 | : "(1 - flag" + flag + ")"; 28 | 29 | emission.push( 30 | "change PC by " + 31 | flagNum + 32 | " * (M + 256 * ( - 1))" 33 | ); 34 | 35 | fs.writeFileSync("instructions/" + name, emission.join("\n")); 36 | } 37 | 38 | for(var flag in branches) { 39 | emit(branches[flag][0], flag, false); 40 | emit(branches[flag][1], flag, true); 41 | } 42 | -------------------------------------------------------------------------------- /src/CPU/build-cpu.js: -------------------------------------------------------------------------------- 1 | /* build-cpu.js 2 | * this is the heart of the metaprogram 3 | * at the entrypoint, the instruction set is complete and annotated, 4 | * and the reference document is available. 5 | * We can just sit back, relax, and put together parts :-) 6 | */ 7 | 8 | var fs = require("fs"); 9 | 10 | // avoid excessive disk use 11 | var instruction_cache = {}; 12 | var addressing_cache = {}; 13 | 14 | // read the table in 15 | var table = JSON.parse(fs.readFileSync("bin/table.json").toString()); 16 | 17 | // jump table emission, etc. 18 | var emission = [ 19 | "mapper read PC" 20 | ]; 21 | 22 | var sources = table.map(function(x, i) { 23 | if(x) { 24 | var instruction = []; 25 | 26 | // add stub for addressing mode 27 | // load if not already in-memory 28 | 29 | if(!addressing_cache[x.addressing]) { 30 | addressing_cache[x.addressing] = 31 | fs.readFileSync("addressing/" + x.addressing) 32 | .toString().trim().split("\n"); 33 | 34 | if(addressing_cache[x.addressing][0].trim().length == 0) 35 | addressing_cache[x.addressing] = []; 36 | } 37 | 38 | instruction = instruction.concat(addressing_cache[x.addressing]); 39 | 40 | // add stub for instruction 41 | if(!instruction_cache[x.name]) { 42 | instruction_cache[x.name] = 43 | fs.readFileSync("instructions/" + x.name) 44 | .toString().trim().split("\n"); 45 | } 46 | 47 | // follow the flags 48 | var flags = instruction_cache[x.name][0].replace(/ /g, '').split(','); 49 | var negQ = false, zeroQ = false, carryQ = false, mode = null, operand = null; 50 | 51 | flags.forEach(function(flag) { 52 | if(flag == "N") negQ = true; 53 | else if(flag == "Z") zeroQ = true; 54 | else if(flag == "C") carryQ = true; 55 | else if(["R", "RW", "IMPLIED", "RAW", "BRANCH", "W"].indexOf(flag) > -1) 56 | mode = flag; 57 | else if (["A", "X", "Y", "tmp", "OP"].indexOf(flag) > -1) 58 | operand = flag; 59 | else 60 | console.error("Unknown flag " + flag + " for instruction " + x.name); 61 | }); 62 | 63 | var ins = instruction_cache[x.name].slice(1); 64 | 65 | if(mode == "R") { 66 | instruction.push("mapper read address"); 67 | instruction.push("set OP to M"); 68 | } else if(mode == "RW") { 69 | if(x.addressing == "accumulator") { 70 | operand = "A"; 71 | ins = ins.map(function(q) { 72 | return q.replace(/OP/g, "A"); 73 | }); 74 | } else { 75 | instruction.push("mapper read address"); 76 | instruction.push("set OP to M"); 77 | } 78 | 79 | ins = ins.map(function(q) { 80 | if(x.addressing != "accumulator") { 81 | return q.replace(/set OP to/, "mapper write address"); 82 | } else { 83 | return q; 84 | } 85 | }); 86 | } else if(mode == "W") { 87 | ins = ins.map(function(q) { 88 | return q.replace(/set OP to/, "mapper write address"); 89 | }); 90 | } else if(mode == "RAW") { 91 | ins = ins.map(function(q) { 92 | return q.replace(/OP/g, "address"); 93 | }); 94 | } else if(mode == "IMPLIED" || mode == "BRANCH") { 95 | 96 | } else { 97 | console.error("Unsupported mode " + mode); 98 | } 99 | 100 | // add the actual code of the instruction 101 | instruction = instruction.concat(ins); 102 | 103 | if(negQ) { 104 | instruction.push("set flagN to <" + operand + " > 127>"); 105 | } 106 | 107 | if(zeroQ) { 108 | instruction.push("set flagZ to <" + operand + " = 0>"); 109 | } 110 | 111 | if(carryQ) { 112 | instruction.push("set flagC to <" + operand + " > 255 or " + operand + " < 0>"); 113 | } 114 | 115 | if(x.name != "JMP" && x.name != "JSR" && x.name != "RTI") { 116 | instruction.push("change PC by " + x.size); 117 | } 118 | 119 | // cycle count 120 | if(mode == "BRANCH") { 121 | // TODO: skip 122 | instruction.push("change cycles by 3"); 123 | } else { 124 | if(x.cycles.length == 2) { 125 | // TODO: skip a cycle if needed 126 | instruction.push("change cycles by " + x.cycles[0]); 127 | } else { 128 | instruction.push("change cycles by " + x.cycles); 129 | } 130 | } 131 | 132 | // instruction.push("say '" + x.assembler + "'"); 133 | 134 | return instruction.join("\n"); 135 | } else { 136 | return [ 137 | 'say "Illegal Opcode ' + i + ' used, ignoring." for 2 secs', 138 | 'change PC by 1' 139 | ].join("\n"); 140 | } 141 | }); 142 | 143 | // dump out an 8 level deep BST 144 | console.log(bst(sources, 0, 255).join('\n')); 145 | 146 | function bst(sources, start, end) { 147 | if(start == end) 148 | return [sources[start]]; 149 | 150 | if(start + 1 == end) 151 | return [ 152 | "if M = " + start + " then", 153 | sources[start], 154 | "else", 155 | sources[end], 156 | "end" 157 | ]; 158 | 159 | var emission = ["if M < " + (start+end+1)/2 + " then"] 160 | .concat(bst(sources, start, start + (end-start-1) / 2)) 161 | .concat(["else"]) 162 | .concat(bst(sources, start + (end-start+1) / 2, end)) 163 | .concat(["end"]); 164 | 165 | return emission; 166 | } 167 | -------------------------------------------------------------------------------- /src/CPU/build-crement.js: -------------------------------------------------------------------------------- 1 | /* INC style instructions */ 2 | 3 | var fs = require("fs"); 4 | 5 | var registers = { 6 | "M": ["INC", "DEC"], 7 | "X": ["INX", "DEX"], 8 | "Y": ["INY", "DEY"] 9 | }; 10 | 11 | for(var reg in registers) { 12 | emit(reg, registers[reg][0], 1); 13 | emit(reg, registers[reg][1], 255); 14 | } 15 | 16 | function emit(register, name, value) { 17 | var emission = []; 18 | 19 | if(register == "M") { 20 | emission.push("RW, N, Z, OP"); 21 | emission.push("set OP to (OP + " + value + ") mod 256"); 22 | } else { 23 | emission.push("IMPLIED,N,Z," + register); 24 | emission.push("set " + register + " to (" + register + " + " + value + ") mod 256"); 25 | } 26 | 27 | fs.writeFileSync("instructions/" + name, emission.join("\n")); 28 | } 29 | -------------------------------------------------------------------------------- /src/CPU/build-lookups.js: -------------------------------------------------------------------------------- 1 | /* 2 | * build-lookups.js 3 | * builds lookup tables for bitwise operations 4 | */ 5 | 6 | var fs = require("fs"); 7 | 8 | emit("EOR", function(a,b){return a^b}); 9 | emit("ORA", function(a,b){return a|b}); 10 | emit("AND", function(a,b){return a&b}); 11 | emit("hex", function(a,b){return ("00" + ((a<<4)|b).toString(16)).substr(-2, 2)}); 12 | emit("bitmask", function(a,b){return ("00000000"+((a<<4)|b).toString(2)).substr(-8,8)}); 13 | emit("obitmask", function(a,b){return "1" + (("00000000"+((a<<4)|b).toString(2)).substr(-8,8))}); 14 | emit("dobitmask", function(a,b){return 2 * ("1" + (("00000000"+((a<<4)|b).toString(2)).substr(-8,8)))}); 15 | 16 | function emit(name, func) { 17 | var emission = []; 18 | 19 | for(var i = 0; i < 256; ++i) { 20 | var a = (i & 0xF0) >> 4; 21 | var b = (i & 0x0F) >> 0; 22 | emission.push(func(a,b)); 23 | } 24 | 25 | fs.writeFileSync("bin/" + name + ".txt", emission.join("\n")); 26 | } 27 | -------------------------------------------------------------------------------- /src/CPU/build-reference.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Original copy of tyhe instruction set 4 | ORIGIN=http://e-tradition.net/bytes/6502/6502_instruction_set.html 5 | 6 | # Fetch reference 7 | wget $ORIGIN 8 | mv 6502_instruction_set.html bin/reference.html 9 | 10 | # trim out plain text -- first 200 lines 11 | tail -n +201 bin/reference.html | head -n -54 > bin/reference.txt 12 | -------------------------------------------------------------------------------- /src/CPU/build-transfer.js: -------------------------------------------------------------------------------- 1 | /* builds TAX-family instructions */ 2 | /* note these get a little funky because of differences in encoding */ 3 | 4 | var fs = require("fs"); 5 | 6 | var list = ["AX", "AY", "SX", "XA", "XS", "YA"]; 7 | list.forEach(emit); 8 | 9 | function emit(name) { 10 | var emission = [ 11 | "IMPLIED" 12 | ]; 13 | 14 | emission.push("set " + name[1] + " to " + name[0]); 15 | 16 | fs.writeFileSync("instructions/T" + name, emission.join("\n")); 17 | } 18 | -------------------------------------------------------------------------------- /src/CPU/build_bitwise.js: -------------------------------------------------------------------------------- 1 | /* build-bitwise.js 2 | * builds the 3 bitwise ops 3 | * this has dirty coercion tricks, unfortunately 4 | */ 5 | 6 | var fs = require("fs"); 7 | 8 | var ops = ["EOR", "ORA", "AND"]; 9 | ops.forEach(emit); 10 | 11 | function emit(op) { 12 | var emission = [ 13 | "R,N,Z,A", 14 | "set hA to item A+1 of hex", 15 | "set hOP to item OP+1 of hex", 16 | 'set A to (16*(item (join "0x" (join (letter 1 of hA) (letter 1 of hOP)))+1 of ' + op + ')) + (item (join "0x" (join (letter 2 of hA) (letter 2 of hOP)))+1 of ' + op + ')' 17 | ]; 18 | 19 | fs.writeFileSync("instructions/" + op, emission.join("\n")); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/CPU/common.tosh: -------------------------------------------------------------------------------- 1 | define ; [comment] 2 | 3 | define-atomic format prg 4 | format prg 128 5 | 6 | define-atomic format prg 128 7 | delete all of oPRG 8 | set tmp to 1 9 | repeat 16384 10 | add (join "0x" (item tmp of PRG-ROM)) to oPRG 11 | change tmp by 1 12 | end 13 | set tmp to 1 14 | repeat 16384 15 | add (join "0x" (item tmp of PRG-ROM)) to oPRG 16 | change tmp by 1 17 | end 18 | 19 | define-atomic format prg 256 20 | delete all of oPRG 21 | set tmp to 1 22 | repeat 32768 23 | add (join "0x" (item tmp of PRG-ROM)) to oPRG 24 | change tmp by 1 25 | end 26 | 27 | define-atomic mapper read (addr) 28 | if addr > 32767 then 29 | set M to item (addr - 32767) of oPRG 30 | else 31 | if addr < 8192 then 32 | set M to item (addr mod 2048 + 1) of RAM 33 | else 34 | if addr > 8192 and addr < 8200 then 35 | read PPU register (addr - 8192) 36 | else 37 | if addr = 16406 then 38 | read controller 1 39 | end 40 | end 41 | end 42 | end 43 | 44 | define-atomic mapper write (addr) (value) 45 | if addr < 8192 then 46 | replace item addr mod 2048 + 1 of RAM with value 47 | else 48 | if addr > 8191 and addr < 8200 then 49 | write PPU register (addr - 8192) value: (value) 50 | else 51 | if addr = 16406 then 52 | controller strobe (value) 53 | else 54 | if addr = 16404 then 55 | OAM DMA (value) 56 | else 57 | if addr > 32768 then 58 | say "Writing to ROM isn't very nice, you know..." 59 | end 60 | end 61 | end 62 | end 63 | end 64 | 65 | define-atomic PLP 66 | set flagN to 67 | set flagV to 68 | set flagB to 69 | set flagD to 70 | set flagI to 71 | set flagZ to 72 | set flagC to 73 | 74 | define-atomic interrupt: vector [vector] 75 | mapper write (256 + S) ((PC - PC mod 256) / 256) 76 | set S to (S - 1) mod 256 77 | mapper write (256 + S) (PC mod 256) 78 | set S to (S - 1) mod 256 79 | set SR to 128 * flagN + 64 * flagV + 32 + 16 * flagB + 8 * flagD + 4 * flagI + 2 * flagZ + 1 * flagC 80 | mapper write (256 + S) (SR) 81 | set S to (S - 1) mod 256 82 | set flagI to 1 83 | mapper read (vector) 84 | set tmp to M 85 | mapper read (vector + 1) 86 | set PC to M * 256 + tmp 87 | 88 | when flag clicked 89 | set A to 0 90 | set X to 0 91 | set Y to 0 92 | set S to "0xFD" 93 | mapper read 65532 94 | set PC to M 95 | mapper read 65533 96 | change PC by M * 256 97 | delete all of RAM 98 | repeat 2048 99 | add "0" to RAM 100 | end 101 | initialize PPU 102 | 103 | when p key pressed 104 | step CPU 105 | 106 | when f key pressed 107 | emulate frame 108 | emulate frame 109 | emulate frame 110 | emulate frame 111 | set line to 0 112 | show variable PC 113 | show variable opcode 114 | reset timer 115 | forever 116 | emulate frame 117 | skip frame 118 | skip frame 119 | skip frame 120 | skip frame 121 | skip frame 122 | skip frame 123 | skip frame 124 | skip frame 125 | skip frame 126 | skip frame 127 | skip frame 128 | skip frame 129 | skip frame 130 | change line by 12 131 | set opcode to line / timer 132 | end 133 | 134 | define-atomic step CPU 135 | if PC > 32767 then 136 | set M to item (PC - 32767) of oPRG 137 | else 138 | if PC < 8192 then 139 | set M to item (PC mod 2048 + 1) of RAM 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /src/CPU/flag-maker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * emits clc-family instructions 3 | */ 4 | 5 | var fs = require("fs"); 6 | 7 | var flags = { 8 | "C": ["CLC", "SEC"], 9 | "D": ["CLD", "SED"], 10 | "I": ["CLI", "SEI"], 11 | "V": ["CLV", null], 12 | }; 13 | 14 | for(var flag in flags) { 15 | if(flags[flag][0]) emit(flags[flag][0], flag, 0); 16 | if(flags[flag][1]) emit(flags[flag][1], flag, 1); 17 | } 18 | 19 | function emit(name, flag, value) { 20 | var emissions = [ 21 | "IMPLIED", 22 | "set flag" + flag + " to " + value 23 | ]; 24 | 25 | fs.writeFileSync("instructions/" + name, emissions.join("\n")); 26 | } 27 | -------------------------------------------------------------------------------- /src/CPU/instructions/ADC: -------------------------------------------------------------------------------- 1 | R,N,Z,A 2 | set temp to A 3 | set tmp to A + OP + flagC 4 | set A to (tmp mod 256) 5 | set flagC to 255> 6 | set flagV to < 128> or 127 and OP > 127 and A < 128>> 7 | -------------------------------------------------------------------------------- /src/CPU/instructions/ASL: -------------------------------------------------------------------------------- 1 | RW,N,Z,OP 2 | set flagC to 127> 3 | set OP to (OP * 2) mod 256 4 | -------------------------------------------------------------------------------- /src/CPU/instructions/BIT: -------------------------------------------------------------------------------- 1 | R, N, OP 2 | set flagV to <(OP mod 128) > 63> 3 | set hA to item A+1 of hex 4 | set hOP to item OP+1 of hex 5 | set flagZ to <(16*(item (join "0x" (join (letter 1 of hA) (letter 1 of hOP)))+1 of AND)) + (item (join "0x" (join (letter 2 of hA) (letter 2 of hOP)))+1 of AND) = 0> 6 | -------------------------------------------------------------------------------- /src/CPU/instructions/BRK: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | mapper write (256 + S) (PC + 2) 3 | set S to (S - 1) mod 256 4 | set SR to (128*flagN) + (64*flagV) + 32 + (16*flagB) + (8*flagD) + (4*flagI) + (2*flagZ) + (1*flagC) 5 | mapper write (256 + S) SR 6 | set S to (S - 1) mod 256 7 | interrupt: vector "0xFFFE" 8 | -------------------------------------------------------------------------------- /src/CPU/instructions/CMP: -------------------------------------------------------------------------------- 1 | R 2 | set flagC to 3 | set flagZ to 4 | set flagN to <((A + (256 - OP)) mod 256) > 127> 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/CPX: -------------------------------------------------------------------------------- 1 | R 2 | set flagC to 3 | set flagZ to 4 | set flagN to <(X + (256 - OP)) mod 256 > 127> 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/CPY: -------------------------------------------------------------------------------- 1 | R 2 | set flagC to 3 | set flagZ to 4 | set flagN to <(Y + (256 - OP)) mod 256 > 127> 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/JMP: -------------------------------------------------------------------------------- 1 | RAW 2 | set PC to OP 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/JSR: -------------------------------------------------------------------------------- 1 | RAW 2 | set tmp to PC + 2 3 | mapper write (256 + S) ( (tmp - (tmp mod 256)) / 256) 4 | set S to (S - 1) mod 256 5 | mapper write (256 + S) (tmp mod 256) 6 | set S to (S - 1) mod 256 7 | set PC to OP 8 | -------------------------------------------------------------------------------- /src/CPU/instructions/LDA: -------------------------------------------------------------------------------- 1 | R, N, Z, A 2 | set A to OP 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/LDX: -------------------------------------------------------------------------------- 1 | R, N, Z, X 2 | set X to OP 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/LDY: -------------------------------------------------------------------------------- 1 | R, N, Z, Y 2 | set Y to OP 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/LSR: -------------------------------------------------------------------------------- 1 | RW, Z, OP 2 | set flagC to OP mod 2 3 | set OP to floor of OP / 2 4 | -------------------------------------------------------------------------------- /src/CPU/instructions/NOP: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | -------------------------------------------------------------------------------- /src/CPU/instructions/PHA: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | mapper write 256 + S A 3 | set S to (S - 1) mod 256 4 | -------------------------------------------------------------------------------- /src/CPU/instructions/PHP: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | set SR to (128*flagN) + (64*flagV) + 32 + 16 + (8*flagD) + (4*flagI) + (2*flagZ) + (1*flagC) 3 | mapper write (256 + S) SR 4 | set S to (S - 1) mod 256 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/PLA: -------------------------------------------------------------------------------- 1 | IMPLIED, N, Z, A 2 | mapper read (257 + S) 3 | set S to (S + 1) mod 256 4 | set A to M 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/PLP: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | mapper read (257 + S) 3 | set S to (S + 1) mod 256 4 | set M to item (M+1) of hex 5 | PLP 6 | -------------------------------------------------------------------------------- /src/CPU/instructions/ROL: -------------------------------------------------------------------------------- 1 | RW, N, Z, OP 2 | set tmp to 127> 3 | set OP to OP * 2 + flagC 4 | set flagC to tmp 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/ROR: -------------------------------------------------------------------------------- 1 | RW, N, Z, OP 2 | set tmp to 3 | set OP to (floor of OP / 2) + (128 * flagC) 4 | set flagC to tmp 5 | -------------------------------------------------------------------------------- /src/CPU/instructions/RTI: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | mapper read (257 + S) 3 | set S to (S + 1) mod 256 4 | PLP 5 | mapper read (257 + S) 6 | set S to (S + 1) mod 256 7 | set tmp to M 8 | mapper read (257 + S) 9 | set S to (S + 1) mod 256 10 | set PC to (M*256) + tmp 11 | -------------------------------------------------------------------------------- /src/CPU/instructions/RTS: -------------------------------------------------------------------------------- 1 | IMPLIED 2 | mapper read (257 + S) 3 | set S to (S + 1) mod 256 4 | set tmp to M 5 | mapper read (257 + S) 6 | set S to (S + 1) mod 256 7 | set PC to (M*256) + tmp 8 | -------------------------------------------------------------------------------- /src/CPU/instructions/SBC: -------------------------------------------------------------------------------- 1 | R,N,Z,A 2 | set temp to A 3 | set tmp to A + 255 - OP + flagC 4 | set A to tmp mod 256 5 | set flagC to 255> 6 | set flagV to < 128> or 127 and OP > 127 and A < 128>> 7 | -------------------------------------------------------------------------------- /src/CPU/instructions/STA: -------------------------------------------------------------------------------- 1 | W 2 | set OP to A 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/STX: -------------------------------------------------------------------------------- 1 | W 2 | set OP to X 3 | -------------------------------------------------------------------------------- /src/CPU/instructions/STY: -------------------------------------------------------------------------------- 1 | W 2 | set OP to Y 3 | -------------------------------------------------------------------------------- /src/CPU/parse-reference.js: -------------------------------------------------------------------------------- 1 | /** 2 | * parse-reference.js 3 | * argv[2] is a text-only 6502 reference sheet 4 | */ 5 | 6 | var fs = require("fs"); 7 | PolyFill(); 8 | 9 | var reference = fs.readFileSync(process.argv[2]) 10 | .toString() 11 | .split("\n"); 12 | 13 | // an instruction is delimited by a character on column 0 14 | // split into instructions 15 | 16 | var instructions = [[]]; 17 | var temp = 0; 18 | 19 | for(var i = 0; i < reference.length; ++i) { 20 | if(reference[i].length == 0 || reference[i][0] == " ") { 21 | instructions[temp].push(reference[i]); 22 | } else { 23 | instructions.push([reference[i]]); 24 | ++temp; 25 | } 26 | } 27 | 28 | // remove car 29 | instructions = instructions.slice(1); 30 | 31 | // parse instructions individually 32 | var opcodes = []; 33 | for(var i = 0; i < instructions.length; ++i) { 34 | opcodes = opcodes.concat(parseInstruction(instructions[i])); 35 | } 36 | 37 | // sort by opcode 38 | var table = []; 39 | for(var j = 0; j < 256; ++j) { table.push(null); } 40 | 41 | opcodes.forEach(function(opcode) { 42 | table[opcode.opcode] = opcode; 43 | }); 44 | 45 | fs.writeFileSync("bin/table.json", JSON.stringify(table)); 46 | 47 | function parseInstruction(instruction) { 48 | // get instruction name 49 | var name = instruction[0].split(" ")[0]; 50 | 51 | // find the opcode list 52 | var nums = instruction.findIndex(function(el) { 53 | return el.indexOf("---------") > -1; 54 | }); 55 | 56 | var forms = instruction.slice(nums + 1, -2).map(function(form) { 57 | var addressing = form.slice(6-1, 20-1).trim(); 58 | var assembler = form.slice(20-1, 34-1).trim(); 59 | var opcode = ("0x" + form.slice(34-1, 40-1).trim()) * 1; 60 | var size = form.slice(40-1, 46-1) * 1; 61 | var cycles = form.slice(46-1).trim(); 62 | 63 | return { 64 | "name": name, 65 | "addressing": addressing, 66 | "size": size, 67 | "opcode": opcode, 68 | "cycles": cycles, 69 | "assembler": assembler 70 | }; 71 | }); 72 | 73 | return forms; 74 | } 75 | 76 | function PolyFill() { 77 | if (!Array.prototype.findIndex) { 78 | Array.prototype.findIndex = function(predicate) { 79 | if (this === null) { 80 | throw new TypeError('Array.prototype.findIndex called on null or undefined'); 81 | } 82 | if (typeof predicate !== 'function') { 83 | throw new TypeError('predicate must be a function'); 84 | } 85 | var list = Object(this); 86 | var length = list.length >>> 0; 87 | var thisArg = arguments[1]; 88 | var value; 89 | 90 | for (var i = 0; i < length; i++) { 91 | value = list[i]; 92 | if (predicate.call(thisArg, value, i, list)) { 93 | return i; 94 | } 95 | } 96 | return -1; 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Input/input.tosh: -------------------------------------------------------------------------------- 1 | define read controller 1 2 | set M to item 1 of controller 1 state * 1 3 | delete 1 of controller 1 state 4 | 5 | define controller strobe (value) 6 | if controller strobe mod 2 = 1 and value mod 2 = 0 then 7 | delete all of controller 1 state 8 | add to controller 1 state 9 | add to controller 1 state 10 | add to controller 1 state 11 | add to controller 1 state 12 | add to controller 1 state 13 | add to controller 1 state 14 | add to controller 1 state 15 | add to controller 1 state 16 | set controller strobe to value 17 | else 18 | set controller strobe to value 19 | end 20 | -------------------------------------------------------------------------------- /src/PPU/PPU.tosh: -------------------------------------------------------------------------------- 1 | define-atomic evaluate sprites scanline: (N) 2 | delete all of secondary OAM 3 | delete all of evaluation line 4 | delete all of secondary bitmaps 5 | delete all of secondary Xt 6 | delete all of sprite 0 7 | repeat 32 8 | add "255" to secondary OAM 9 | add "-1" to evaluation line 10 | add "-1" to evaluation line 11 | add "-1" to evaluation line 12 | add "-1" to evaluation line 13 | add "-1" to evaluation line 14 | add "-1" to evaluation line 15 | add "-1" to evaluation line 16 | add "-1" to evaluation line 17 | end 18 | set evaluation n to 1 19 | set evaluation slot to 1 20 | repeat 64 21 | if evaluation slot < 32 and not N < item evaluation n of OAM and item evaluation n of OAM + 8 > N then 22 | set temp to item (evaluation n + 3) of OAM 23 | replace item temp of evaluation line with evaluation slot 24 | replace item temp + 1 of evaluation line with evaluation slot 25 | replace item temp + 2 of evaluation line with evaluation slot 26 | replace item temp + 3 of evaluation line with evaluation slot 27 | replace item temp + 4 of evaluation line with evaluation slot 28 | replace item temp + 5 of evaluation line with evaluation slot 29 | replace item temp + 6 of evaluation line with evaluation slot 30 | replace item temp + 7 of evaluation line with evaluation slot 31 | get pattern tile: (item (evaluation n + 1) of OAM) scanline: (mY - item evaluation n of OAM) table: (PPU Sprite pattern table) 32 | add mask to secondary bitmaps 33 | add temp - 3 to secondary Xt 34 | add to sprite 0 35 | change evaluation slot by 1 36 | end 37 | change evaluation n by 4 38 | end 39 | ; "TODO: evaluate sprites in secondary OAM" 40 | ; "TODO: buggy sprite overflow flag" 41 | 42 | define-atomic get pattern tile: (tile) scanline: (scanline) table: (table) 43 | set mask to item (16 * tile + scanline + table + 1) of oCHR 44 | 45 | ; "optimizes CHR-ROM" 46 | 47 | define-atomic format chr 48 | delete all of oCHR 49 | set temp to 1 50 | repeat length of CHR-ROM 51 | set BG: Plane 0 to item (1 + (join "0x" (item temp of CHR-ROM))) of bitmask 52 | set BG: Plane 1 to item (1 + (join "0x" (item (temp + 8) of CHR-ROM))) of bitmask 53 | set BG: Plane 0 to (join "1" (BG: Plane 0)) 54 | set BG: Plane 1 to (join "1" (BG: Plane 1)) 55 | add BG: Plane 0 + BG: Plane 1 * 2 to oCHR 56 | change temp by 1 57 | end 58 | if length of CHR-ROM = 0 then 59 | repeat 8192 60 | add "0" to CHR-ROM 61 | add "0" to oCHR 62 | end 63 | end 64 | 65 | when q key pressed 66 | emulate frame 67 | 68 | define-atomic VBlank 69 | set PPU vblank to 1 70 | if PPU generate NMI = 1 then 71 | interrupt: vector "0xFFFA" 72 | end 73 | repeat until cycles > 2380 74 | step CPU 75 | end 76 | set cycles to 0 77 | 78 | define-atomic skip frame 79 | ; "TODO: sprite 0 hit etc?" 80 | set mY to -1 81 | repeat 224 82 | change mY by 1 83 | repeat until mX > 254 84 | step CPU 85 | change mX by 3 * cycles 86 | set cycles to 0 87 | end 88 | ; "TODO: HBlank" 89 | end 90 | VBlank 91 | 92 | define-atomic emulate frame 93 | go to x: -128 y: 128 94 | set mY to -1 95 | set adjacent count to 0 96 | set adjacent color to -1 97 | set PPU sprite 0 to 0 98 | repeat 224 99 | change mY by 1 100 | evaluate sprites scanline: (mY) 101 | go to x: -128 y: y position - 1 102 | pen down 103 | set mX to 0 104 | repeat until mX > 254 105 | step CPU 106 | step CPU 107 | if PPU show bg = 1 or PPU show sprites = 1 then 108 | repeat cycles 109 | if mX mod 8 = 0 then 110 | read PPU memory @ (PPU base nametable address + mX / 8 + 32 * floor of (mY / 8)) 111 | set BG: Plane 0 to item (16 * M + PPU Background pattern table + mY mod 8 + 1) of oCHR 112 | read PPU memory @ (PPU base nametable address + 960 + floor of (mX / 32) + 8 * floor of (mY / 32)) 113 | set offset to 2 * 15> + 4 * 15> 114 | set BG: Palette to 8 * letter (offset + 1) of item (1 + M) of bitmask + 4 * letter (offset + 2) of item (1 + M) of bitmask + 1 115 | end 116 | if item (mX + 1) of evaluation line < 1 then 117 | set color to item (BG: Palette + letter (2 + mX mod 8) of BG: Plane 0) of Palette 118 | else 119 | change PPU sprite 0 by item item (mX + 1) of evaluation line of sprite 0 120 | set tmp to letter (mX - item item (mX + 1) of evaluation line of secondary Xt) of item item (mX + 1) of evaluation line of secondary bitmaps 121 | if tmp = 0 then 122 | set color to item (BG: Palette + letter (2 + mX mod 8) of BG: Plane 0) of Palette 123 | else 124 | set color to item (17 + tmp) of Palette 125 | end 126 | end 127 | change mX by 1 128 | if adjacent color = color then 129 | change adjacent count by 1 130 | else 131 | set pen color to (adjacent color) 132 | change x by adjacent count 133 | set adjacent count to 1 134 | set adjacent color to color 135 | end 136 | end 137 | else 138 | change mX by 3 * cycles 139 | end 140 | set cycles to 0 141 | end 142 | if adjacent count > 0 then 143 | set pen color to (adjacent color) 144 | change x by adjacent count 145 | set adjacent count to 0 146 | end 147 | pen up 148 | end 149 | VBlank 150 | 151 | define-atomic initialize PPU 152 | pen up 153 | clear 154 | set pen size to 1 155 | delete all of OAM 156 | repeat 256 157 | add "0" to OAM 158 | end 159 | delete all of Nametables 160 | repeat 960 161 | add "0x24" to Nametables 162 | end 163 | repeat 2048 - 960 164 | add "0" to Nametables 165 | end 166 | delete all of Palette 167 | repeat 1 + 4 * 4 + 4 * 4 168 | add "0" to Palette 169 | end 170 | hide 171 | 172 | define-atomic read PPU register (N) 173 | if N = 2 then 174 | set M to 128 * PPU vblank + 64 * 0> + 32 * PPU sprite overflow 175 | set PPU address latch to 0 176 | ; "set PPU vblank to 0" 177 | else 178 | if N = 7 then 179 | read PPU memory 180 | end 181 | end 182 | 183 | ; "TODO: use O(logN) lookup instead of O(N)" 184 | 185 | define-atomic write PPU register (N) value: (V) 186 | if N = 0 then 187 | set mask to item (1 + V) of bitmask 188 | set PPU base nametable address to 8192 + ((2 * letter 7 of mask + letter 8 of mask) * 1024) 189 | set PPU VRAM increment to letter 6 of mask * 31 + 1 190 | set PPU Sprite pattern table to letter 5 of mask * 4096 191 | set PPU Background pattern table to letter 4 of mask * 4096 192 | set PPU Sprite size to letter 3 of mask 193 | set PPU master slave select to letter 2 of mask 194 | set PPU generate NMI to letter 1 of mask 195 | else 196 | if N = 1 then 197 | set mask to item (1 + V) of bitmask 198 | set PPU grayscale to letter 8 of mask 199 | set PPU show left8 bg to letter 7 of mask 200 | set PPU show left8 sprites to letter 6 of mask 201 | set PPU show bg to letter 5 of mask 202 | set PPU show sprites to letter 4 of mask 203 | set PPU emphasize blue to letter 3 of mask 204 | set PPU emphasize green to letter 2 of mask 205 | set PPU emphasize red to letter 1 of mask 206 | else 207 | if N = 3 then 208 | set PPU OAMADDR to V 209 | else 210 | if N = 4 then 211 | replace item PPU OAMADDR mod 256 + 1 of OAM with V 212 | change PPU OAMADDR by 1 213 | else 214 | if N = 5 then 215 | if PPU address latch = 0 then 216 | set PPU fine x scroll to V 217 | else 218 | set PPU fine y scroll to V 219 | end 220 | else 221 | if N = 6 then 222 | if PPU address latch = 0 then 223 | ; "TODO: is this correct?" 224 | set PPU VRAM address to V * 256 225 | set PPU address latch to 1 226 | else 227 | change PPU VRAM address by V 228 | set PPU address latch to 0 229 | end 230 | else 231 | if N = 7 then 232 | write PPU memory (V) 233 | change PPU VRAM address by 1 234 | end 235 | end 236 | end 237 | end 238 | end 239 | end 240 | end 241 | 242 | define-atomic OAM DMA (pagebase) 243 | set temp to 0 244 | repeat 256 245 | mapper read (256 * pagebase + PPU OAMADDR + temp) 246 | change temp by 1 247 | replace item temp of OAM with M 248 | end 249 | change cycles by 513 250 | 251 | define-atomic read PPU memory 252 | if PPU VRAM address < 32 * 256 then 253 | set M to item (1 + PPU VRAM address) of CHR-ROM 254 | else 255 | if PPU VRAM address < 48 * 256 then 256 | set M to item (PPU VRAM address - 8191) of Nametables 257 | else 258 | if PPU VRAM address < 63 * 256 then 259 | set M to item (PPU VRAM address - 12287) of Nametables 260 | else 261 | if PPU VRAM address < 64 * 256 then 262 | set M to item (PPU VRAM address mod 32 + 1) of Palette 263 | else 264 | ; "TODO: PPU memory mirroring" 265 | end 266 | end 267 | end 268 | end 269 | 270 | define-atomic read PPU memory @ [VRAM address] 271 | if VRAM address < 32 * 256 then 272 | set M to item (1 + VRAM address) of CHR-ROM 273 | else 274 | if VRAM address < 48 * 256 then 275 | set M to item (VRAM address - 8191) of Nametables 276 | else 277 | if VRAM address < 63 * 256 then 278 | set M to item (VRAM address - 12287) of Nametables 279 | else 280 | if VRAM address < 64 * 256 then 281 | set M to item (VRAM address mod 32 + 1) of Palette 282 | else 283 | ; "TODO: PPU memory mirroring" 284 | end 285 | end 286 | end 287 | end 288 | 289 | define-atomic write PPU memory (V) 290 | if PPU VRAM address < 32 * 256 then 291 | replace item 1 + PPU VRAM address of CHR-ROM with item (V + 1) of hex 292 | ; "TODO: debug completely" 293 | if PPU VRAM address mod 16 < 8 then 294 | set BG: Plane 0 to item (1 + (join "0x" (item (1 + PPU VRAM address) of CHR-ROM))) of bitmask 295 | set BG: Plane 1 to item (1 + (join "0x" (item (1 + PPU VRAM address + 8) of CHR-ROM))) of bitmask 296 | set BG: Plane 0 to (join "1" (BG: Plane 0)) 297 | set BG: Plane 1 to (join "1" (BG: Plane 1)) 298 | replace item 1 + PPU VRAM address of oCHR with BG: Plane 0 + BG: Plane 1 * 2 299 | else 300 | set BG: Plane 0 to item (1 + (join "0x" (item (1 + PPU VRAM address - 8) of CHR-ROM))) of bitmask 301 | set BG: Plane 1 to item (1 + (join "0x" (item (1 + PPU VRAM address) of CHR-ROM))) of bitmask 302 | set BG: Plane 0 to (join "1" (BG: Plane 0)) 303 | set BG: Plane 1 to (join "1" (BG: Plane 1)) 304 | replace item 1 + PPU VRAM address - 8 of oCHR with BG: Plane 0 + BG: Plane 1 * 2 305 | end 306 | else 307 | if PPU VRAM address < 48 * 256 then 308 | replace item PPU VRAM address - 8191 of Nametables with V 309 | else 310 | if PPU VRAM address < 63 * 256 then 311 | replace item PPU VRAM address - 12287 of Nametables with V 312 | else 313 | if PPU VRAM address < 64 * 256 then 314 | if PPU VRAM address mod 32 = 0 or < and <(PPU VRAM address - 1) mod 4 = 0>> then 315 | set temp to item (V + 1) of RGB 316 | replace item 1 of Palette with temp 317 | replace item 5 of Palette with temp 318 | replace item 9 of Palette with temp 319 | replace item 13 of Palette with temp 320 | replace item 17 of Palette with temp 321 | replace item 21 of Palette with temp 322 | replace item 25 of Palette with temp 323 | replace item 29 of Palette with temp 324 | else 325 | replace item PPU VRAM address mod 32 + 1 of Palette with item (V + 1) of RGB 326 | end 327 | else 328 | ; "TODO: PPU memory mirroring" 329 | ask V and wait 330 | end 331 | end 332 | end 333 | end 334 | -------------------------------------------------------------------------------- /src/PPU/README.md: -------------------------------------------------------------------------------- 1 | This directory has the PPU for ScratchNES 2 | 3 | Palette 4 | ========== 5 | 6 | The NES internally uses a composite based palette as opposed to RGB. `sRGB.txt` contains an approximate sRGB palette mapping from the [NESdev wiki](http://wiki.nesdev.com/w/index.php/Palette). `build_palette.js` builds an optimized version from this file for Scratch. 7 | -------------------------------------------------------------------------------- /src/PPU/build_palette.js: -------------------------------------------------------------------------------- 1 | /** 2 | * inputs the sRGB palette; 3 | * emits the Scratch format lookup 4 | */ 5 | 6 | var fs = require("fs"); 7 | var palette = fs.readFileSync(process.argv[2]) 8 | .toString() 9 | .split("\n") 10 | .map(function(str) { 11 | var arr = []; 12 | for(var i = 0; i < str.length; i += 13) { 13 | arr.push(str.slice(i, i+13) 14 | .trim() 15 | .split(/[ ]+/) 16 | .map(function(x) { 17 | return x*1; 18 | })); 19 | } 20 | return arr.map(function(x) { 21 | return (65536*x[0]) + (256*x[1]) + x[2]; 22 | }); 23 | }); 24 | 25 | console.log([].concat.apply([], palette).join("\n")); 26 | -------------------------------------------------------------------------------- /src/PPU/sRGB.txt: -------------------------------------------------------------------------------- 1 | 84 84 84 0 30 116 8 16 144 48 0 136 68 0 100 92 0 48 84 4 0 60 24 0 32 42 0 8 58 0 0 64 0 0 60 0 0 50 60 0 0 0 0 0 0 0 0 0 2 | 152 150 152 8 76 196 48 50 236 92 30 228 136 20 176 160 20 100 152 34 32 120 60 0 84 90 0 40 114 0 8 124 0 0 118 40 0 102 120 0 0 0 0 0 0 0 0 0 3 | 236 238 236 76 154 236 120 124 236 176 98 236 228 84 236 236 88 180 236 106 100 212 136 32 160 170 0 116 196 0 76 208 32 56 204 108 56 180 204 60 60 60 0 0 0 0 0 0 4 | 236 238 236 168 204 236 188 188 236 212 178 236 236 174 236 236 174 212 236 180 176 228 196 144 204 210 120 180 222 120 168 226 144 152 226 180 160 214 228 160 162 160 0 0 0 0 0 0 5 | -------------------------------------------------------------------------------- /src/ROM/ROM.tosh: -------------------------------------------------------------------------------- 1 | when 1 key pressed 2 | ask "Cual?" and wait 3 | if answer = "format chr" then 4 | format chr 5 | else 6 | if answer = "format prg" then 7 | format prg 8 | else 9 | if answer = "irom" then 10 | ask "PRG?" and wait 11 | set temp to answer 12 | ask "CHR?" and wait 13 | unpack ROM with (temp) banks of PRG-ROM and (answer) banks of CHR-ROM 14 | end 15 | end 16 | end 17 | 18 | define-atomic unpack ROM with (N) banks of PRG-ROM and (P) banks of CHR-ROM 19 | set temp to 17 20 | delete all of PRG-ROM 21 | delete all of CHR-ROM 22 | repeat (N*16384) 23 | add item temp of iNES ROM to PRG-ROM 24 | change temp by 1 25 | end 26 | repeat (P*8192) 27 | add item temp of iNES ROM to CHR-ROM 28 | change temp by 1 29 | end 30 | format prg 31 | format chr 32 | -------------------------------------------------------------------------------- /src/ROM/flash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./hexify.sh $1 > $1.hex 3 | node ../tools/tableify.js $1.hex "iNES ROM" 4 | -------------------------------------------------------------------------------- /src/ROM/hexify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | hexdump -v -e '/1 "%02X\n"' $1 3 | -------------------------------------------------------------------------------- /src/build-emulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cat CPU/common.tosh 3 | cd CPU ; node build-cpu.js > ../CPU.tosh ; cd .. 4 | cat CPU.tosh 5 | echo "" 6 | cat PPU/PPU.tosh 7 | echo "" 8 | echo "" 9 | cat Input/input.tosh 10 | echo "" 11 | cat ROM/ROM.tosh 12 | -------------------------------------------------------------------------------- /src/tools/rip.js: -------------------------------------------------------------------------------- 1 | /* rip.js 2 | * rips an NROM-32 .nes file 3 | */ 4 | 5 | var fs = require("fs"); 6 | var name = process.argv[2].split(".").slice(0, -1).join("."); 7 | var game = fs.readFileSync(process.argv[2]); 8 | var PRGROM = game.slice(16, 16 + 16384); 9 | fs.writeFileSync(name + ".hex", PRGROM); 10 | -------------------------------------------------------------------------------- /src/tools/tableify.js: -------------------------------------------------------------------------------- 1 | /* 2 | * automatically fills up a buffer with stuff from text 3 | */ 4 | 5 | var arr = require("fs").readFileSync(process.argv[2]) 6 | .toString() 7 | .split("\n") 8 | .map(function(line) { 9 | return 'add "' + line + '" to ' + process.argv[3]; 10 | }); 11 | 12 | console.log("when q key pressed"); 13 | console.log("delete all of " + process.argv[3]); 14 | console.log(arr.join("\n")); 15 | --------------------------------------------------------------------------------