math.randomseed(os.time()) -- create the module's table local studybox = {} -- import required modules local nes = require "scripts.app.nes" local dict = require "scripts.app.dict" local dump = require "scripts.app.dump" local flash = require "scripts.app.flash" local buffers = require "scripts.app.buffers" -- file constants local mapname = "SBX" -- local functions local function create_header( file, prgKB, chrKB ) --write_header( file, prgKB, chrKB, mapper, mirroring ) nes.write_header( file, prgKB, 0, op_buffer[mapname], 0) end --local function wr_flash_byte(addr, value, debug) --base is the actual NES CPU address, not the rom offset (ie $FFF0, not $7FF0) --local function wr_bank_table(base, entries) --Action53 not susceptible to bus conflicts, no banktable needed local function sb_write_string(start_addr, value, log) value = string.format("'%s' starting at $%04X", value, start_addr) print(string.format("Writing %s starting at address $%04X\n", value, start_addr)) for i = 1, #value do local c = value:sub(i,i) dict.nes("NES_CPU_WR", start_addr + (i - 1), string.byte(c)) end end local function sb_write_and_verify(start_addr, stop_addr, value, log) log:write(string.format("Writing $%X from address $%X to $%X\n", value, start_addr, stop_addr)) addr = start_addr while (addr < stop_addr) do dict.nes("NES_CPU_WR", addr, value) addr = addr + 1 end log:write("Verifying data\n") addr = start_addr while(addr < stop_addr) do rv = dict.nes("NES_CPU_RD", addr) if rv ~= value then log:write(string.format("$%X not $%X\n", addr, value)) end addr = addr + 1 end end -- Write and verify random values in RAM local function sb_random_ram(start_addr, stop_addr, log) log:write(string.format("\nStarting random ram test from address $%04X to address $%04X\n", start_addr, stop_addr)) values = {} for i = 0, (stop_addr - start_addr) do val = math.random(0, 255) values[i] = val dict.nes("NES_CPU_WR", start_addr + i, val) end for i = 0, (stop_addr - start_addr) do rv = dict.nes("NES_CPU_RD", start_addr + i) if rv ~= values[i] then log:write(string.format("$%04X: expected $%02X received $%02X\n", start_addr + i, values[i], rv)) end end end local function sb_write_ram(start_addr, stop_addr, value) print(string.format("Writing $%02X from $%04X to $%04X", value, start_addr, stop_addr)) while (start_addr < stop_addr) do dict.nes("NES_CPU_WR", start_addr, value) start_addr = start_addr + 1 end end local function sb_verify_value_ram(start_addr, stop_addr, value, log) print(string.format("Verifying $%02X from $%04X to $%04X", value, start_addr, stop_addr)) while (start_addr < stop_addr) do rv = dict.nes("NES_CPU_RD", start_addr) if rv ~= value then log:write(string.format("$%04X: expected $%02X received $%02X\n", start_addr, value, rv)) end start_addr = start_addr + 1 end end local function sb_ram_dump(start_addr, stop_addr, filename) file = io.open(filename, "wb") while (start_addr < stop_addr) do rv = dict.nes("NES_CPU_RD", start_addr) file:write(string.char(rv)) start_addr = start_addr + 1 end file:flush() file:close() end -- Write data to ram and read it back local function sb_verify_ram() logfile = io.open("studybox.log", "w") print("Starting sb_verify_ram()") --print("Write/Verify $00") --sb_write_and_verify(0x4020, 0x8000, 0x00, logfile) --print("Write/Verify $88") --sb_write_and_verify(0x4020, 0x8000, 0x88, logfile) --print("Write/Verify $FF") --sb_write_and_verify(0x4020, 0x8000, 0xFF, logfile) --print("Write/Verify random") --sb_random_ram(0x4020, 0x8000, logfile) -- Select first bank --sprint("\n$4200 value of $00") --dict.nes("NES_CPU_WR", 0x4200, 0x00) --val = math.random(0, 255) --sb_write_ram(0x5000, 0x5FFF, val) --sb_verify_value_ram(0x4400, 0x8000, val, logfile) --sprint("\n$4200 value of $01") --sdict.nes("NES_CPU_WR", 0x4200, 0x01) --sval = math.random(0, 255) --ssb_write_ram(0x5000, 0x5FFF, val) --ssb_verify_value_ram(0x4400, 0x8000, val, logfile) --sprint("\n$4200 value of $00 verify") --sdict.nes("NES_CPU_WR", 0x4200, 0x00) --ssb_verify_value_ram(0x4400, 0x5FFF, val, logfile) --print("\n$4200 value of $00") --dict.nes("NES_CPU_WR", 0x4200, 0x00) --val = math.random(0, 255) --sb_write_ram(0x6000, 0x8000, val) --sb_verify_value_ram(0x6000, 0x8000, val, logfile) --logfile:write("\n==== Second verify with $4200 value $40 ====\n") --print("\n$4200 value of $40") --dict.nes("NES_CPU_WR", 0x4200, 0x40) --sb_verify_value_ram(0x6000, 0x8000, val, logfile) print("Clearing ram") sb_write_ram(0x4400, 0x8000, 0x00) --41 b01000001 --82 b10000010 --82 b11000011 dict.nes("NES_CPU_WR", 0x4200, 0x41) sb_write_ram(0x4400, 0x8000, 0x00) dict.nes("NES_CPU_WR", 0x4200, 0x82) sb_write_ram(0x4400, 0x8000, 0x00) dict.nes("NES_CPU_WR", 0x4200, 0xC3) sb_write_ram(0x4400, 0x8000, 0x00) print("Writing strings") dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_write_string(0x5400, "|< page $00 >|") sb_write_string(0x6000, "|< page $00 >|") dict.nes("NES_CPU_WR", 0x4200, 0x41) sb_write_string(0x5000, "|< page $01 >|") sb_write_string(0x6000, "|< page $01 >|") dict.nes("NES_CPU_WR", 0x4200, 0x82) sb_write_string(0x5000, "|< page $02 >|") sb_write_string(0x6000, "|< page $02 >|") dict.nes("NES_CPU_WR", 0x4200, 0xC3) sb_write_string(0x5000, "|< page $03 >|") sb_write_string(0x6000, "|< page $03 >|") --sb_write_string(0x5000, "|< page $00 at $5000 >|") print("dumping 4200_00.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_ram_dump(0x4400, 0x8000, "4200_00.dmp") print("dumping 4200_01.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x01) sb_ram_dump(0x4400, 0x8000, "4200_01.dmp") print("dumping 4200_01.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x01) sb_ram_dump(0x4400, 0x8000, "4200_01.dmp") --print("\n$4200 value of $01") --dict.nes("NES_CPU_WR", 0x4200, 0x01) --sb_ram_dump(0x4400, 0x8000, "4200_01.dmp") --print("\n$4200 value of $02") --dict.nes("NES_CPU_WR", 0x4200, 0x02) --sb_ram_dump(0x4400, 0x8000, "4200_02.dmp") --print("\n$4200 value of $03") --dict.nes("NES_CPU_WR", 0x4200, 0x03) --sb_ram_dump(0x4400, 0x8000, "4200_03.dmp") --print("\n$4200 value of $40") --dict.nes("NES_CPU_WR", 0x4200, 0x40) --sb_ram_dump(0x4400, 0x8000, "4200_40.dmp") --print("\n$4200 value of $80") --dict.nes("NES_CPU_WR", 0x4200, 0x80) --sb_ram_dump(0x4400, 0x8000, "4200_80.dmp") --print("\n$4200 value of $C0") --dict.nes("NES_CPU_WR", 0x4200, 0x80) --sb_ram_dump(0x4400, 0x8000, "4200_C0.dmp") logfile:flush() logfile:close() end --initialize mapper for dump/flash routines local function init_mapper( debug ) --//Setup as CNROM, then scroll through outer banks. --cpu_wr(0x5000, 0x80); //reg select mode --dict.nes("NES_CPU_WR", 0x5000, 0x80) --// xxSSPPMM SS-size: 0-32KB, PP-prg mode: 0,1 32KB, MM-mirror --cpu_wr(0x8000, 0b00000000); //reg value 256KB inner, 32KB banks --dict.nes("NES_CPU_WR", 0x8000, 0x00) --cpu_wr(0x5000, 0x81); //outer reg select mode --dict.nes("NES_CPU_WR", 0x5000, 0x81) --cpu_wr(0x8000, 0x00); //first 32KB bank --dict.nes("NES_CPU_WR", 0x8000, 0x00) -- --cpu_wr(0x5000, 0x01); //inner prg reg select --dict.nes("NES_CPU_WR", 0x5000, 0x01) --cpu_wr(0x8000, 0x00); //controls nothing in this size --dict.nes("NES_CPU_WR", 0x8000, 0x00) --cpu_wr(0x5000, 0x00); //chr reg select --dict.nes("NES_CPU_WR", 0x5000, 0x00) --cpu_wr(0x8000, 0x00); //first chr bank --dict.nes("NES_CPU_WR", 0x8000, 0x00) --selecting CNROM means that mapper writes to $8000-FFFF will only change the CHR-RAM bank which --doesn't affect anything we're concerned about end local function sb_dump_rom() file = io.open("prg.dat", "wb") for i = 0x00,0x0F,1 do print(string.format("$4201: %02X", i)) dict.nes("NES_CPU_WR", 0x4201, i) start_addr = 0x8000 stop_addr = 0xC000 while (start_addr < stop_addr) do rv = dict.nes("NES_CPU_RD", start_addr) file:write(string.char(rv)) start_addr = start_addr + 1 end --sb_ram_dump(0x8000, 0xC000, string.format("raw/prg_8000_%02X.dat", i)) end file:flush() file:close() --dict.nes("NES_CPU_WR", 0x4201, 0x00) --sb_ram_dump(0x8000, 0xC000, "prg_8000_00.dat") --sb_ram_dump(0xC000, 0x10000, "prg_C000_00.dat") --dict.nes("NES_CPU_WR", 0x4201, 0x01) --sb_ram_dump(0x8000, 0xC000, "prg_8000_01.dat") --sb_ram_dump(0xC000, 0x10000, "prg_C000_01.dat") --dict.nes("NES_CPU_WR", 0x4201, 0x02) --sb_ram_dump(0x8000, 0xC000, "prg_8000_02.dat") --sb_ram_dump(0xC000, 0x10000, "prg_C000_02.dat") --dict.nes("NES_CPU_WR", 0x4201, 0x03) --sb_ram_dump(0x8000, 0xC000, "prg_8000_03.dat") --sb_ram_dump(0xC000, 0x10000, "prg_C000_03.dat") end local function sb_write_save() print("Clearing ram") dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_write_ram(0x600, 0x8000, 0x00) sb_write_string(0x5400, "|< page $00 >|") sb_write_string(0x6000, "|< page $00 >|") end local function sb_dump_save() print("Dumping ram") dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_ram_dump(0x4400, 0x8000, "raw/ram.dat") end local function sb_read_registers() data = dict.nes("NES_CPU_RD", 0x4202) print(string.format("$4202: $%02X", data)) end --read PRG-ROM flash ID local function prgrom_manf_id( debug ) init_mapper() if debug then print("reading PRG-ROM manf ID") end --A0-A14 are all directly addressable in CNROM mode --and mapper writes don't affect PRG banking dict.nes("NES_CPU_WR", 0xD555, 0xAA) dict.nes("NES_CPU_WR", 0xAAAA, 0x55) dict.nes("NES_CPU_WR", 0xD555, 0x90) rv = dict.nes("NES_CPU_RD", 0x8000) if debug then print("attempted read PRG-ROM manf ID:", string.format("%X", rv)) end rv = dict.nes("NES_CPU_RD", 0x8001) if debug then print("attempted read PRG-ROM prod ID:", string.format("%X", rv)) end --exit software dict.nes("NES_CPU_WR", 0x8000, 0xF0) end local function mirror_test( debug ) init_mapper() end local function bank0(bank) dict.nes("NES_CPU_WR", 0x4201, bank) print("selecting bank: ", bank) --rv = dict.nes("NES_CPU_RD", 0x8000) --if debug then print("attempted read PRG-ROM $8000:", string.format("%X", rv)) end --rv = dict.nes("NES_CPU_RD", 0x8001) --if debug then print("attempted read PRG-ROM $8001:", string.format("%X", rv)) end end local function sb_write_ppu_string(start_addr, value) value = string.format("'%s' starting at $%04X", value, start_addr) print(string.format("Writing %s starting at address $%04X\n", value, start_addr)) for i = 1, #value do local c = value:sub(i,i) dict.nes("NES_PPU_WR", start_addr + (i - 1), string.byte(c)) end end local function sb_ppu_test() dict.nes("NES_CPU_WR", 0x4200, 0x00) dict.nes("NES_CPU_WR", 0x4201, 0x00) dict.nes("NES_CPU_WR", 0x4203, 0x00) print("Clearing ppu ram") for i=0x0000,0x2FFF,1 do dict.nes("NES_PPU_WR", i, 0x00) end sb_write_ppu_string(0x0000, "|< chr page $00 >|") print("Writing nametable 1") for i=0x2000,0x23FF,1 do dict.nes("NES_PPU_WR", i, 0x01) end print("Writing nametable 2") for i=0x2400,0x27FF,1 do dict.nes("NES_PPU_WR", i, 0x02) end print("Writing nametable 3") for i=0x2800,0x2BFF,1 do dict.nes("NES_PPU_WR", i, 0x03) end print("Writing nametable 4") for i=0x2C00,0x2FFF,1 do dict.nes("NES_PPU_WR", i, 0x04) end print("dumping ppu ram") dict.nes("NES_CPU_WR", 0x4200, 0xFF) dict.nes("NES_CPU_WR", 0x4201, 0xFF) dict.nes("NES_CPU_WR", 0x4203, 0xFF) file = io.open("raw/ppu.dat", "wb") for i=0x0000,0x2FFF,1 do val = dict.nes("NES_PPU_RD", i) file:write(string.char(val)) end file:flush() file:close() end local function sb_ppu_high_ram_test() print("Testing PPU RAM $3000-$3FFF") file = io.open("raw/ppu_high_init.dmp", "wb") for i=0x3000,0x3FFF,1 do val = dict.nes("NES_PPU_RD", i) file:write(string.char(val)) end file:flush() file:close() print("writing $B1") for i=0x3000,0x3FFF,1 do dict.nes("NES_PPU_WR", i, 0xB1) end print("dump 2") file = io.open("raw/ppu_high_post.dmp", "wb") for i=0x3000,0x3FFF,1 do val = dict.nes("NES_PPU_RD", i) file:write(string.char(val)) end file:flush() file:close() print("dump ppu main") file = io.open("raw/ppu_main.dmp", "wb") for i=0x0000,0x2FFF,1 do val = dict.nes("NES_PPU_RD", i) file:write(string.char(val)) end file:flush() file:close() end -- Test CPU vs PPU ram local function sb_ram_full_test() --print("Clearing ram") --sb_write_ram(0x4400, 0x8000, 0x00) --dict.nes("NES_CPU_WR", 0x4200, 0x41) --sb_write_ram(0x4400, 0x8000, 0x00) --dict.nes("NES_CPU_WR", 0x4200, 0x82) --sb_write_ram(0x4400, 0x8000, 0x00) --dict.nes("NES_CPU_WR", 0x4200, 0xC3) --sb_write_ram(0x4400, 0x8000, 0x00) --dict.nes("NES_CPU_WR", 0x4200, 0x00) --dict.nes("NES_CPU_WR", 0x4201, 0x00) --dict.nes("NES_CPU_WR", 0x4203, 0x00) --print("Clearing ppu ram") --for i=0x0000,0x2FFF,1 do -- dict.nes("NES_PPU_WR", i, 0x00) --end print("Writing values") -- Only using 0x6000-0x7FFF here because it's -- the same memory as the first block dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_write_ram(0x6000, 0x7FFF, 01) dict.nes("NES_CPU_WR", 0x4200, 0x40) sb_write_ram(0x6000, 0x7FFF, 02) dict.nes("NES_CPU_WR", 0x4200, 0x80) sb_write_ram(0x6000, 0x7FFF, 03) dict.nes("NES_CPU_WR", 0x4200, 0xC0) sb_write_ram(0x6000, 0x7FFF, 04) --print("Clearing ppu ram") --for i=0x0000,0x2FFF,1 do -- dict.nes("NES_PPU_WR", i, 0x00) --end -- print("Writing values") print("Writing nametable 1") for i=0x2000,0x23FF,1 do dict.nes("NES_PPU_WR", i, 0xA1) end print("Writing nametable 2") for i=0x2400,0x27FF,1 do dict.nes("NES_PPU_WR", i, 0xA2) end print("Writing nametable 3") for i=0x2800,0x2BFF,1 do dict.nes("NES_PPU_WR", i, 0xA3) end print("Writing nametable 4") for i=0x2C00,0x2FFF,1 do dict.nes("NES_PPU_WR", i, 0xA4) end print("writing CHR at 0x0000") for i=0x0000,0x0FFF,1 do dict.nes("NES_PPU_WR", i, 0xA5) end print("writing CHR at 0x1000") for i=0x1000,0x1FFF,1 do dict.nes("NES_PPU_WR", i, 0xA6) end print("dumping ppu ram") --dict.nes("NES_CPU_WR", 0x4200, 0xFF) --dict.nes("NES_CPU_WR", 0x4201, 0xFF) --dict.nes("NES_CPU_WR", 0x4203, 0xFF) file = io.open("raw/cpu-ppu.dmp", "wb") for i=0x0000,0x2FFF,1 do val = dict.nes("NES_PPU_RD", i) file:write(string.char(val)) end file:flush() file:close() print("dumping 4200_00.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x00) sb_ram_dump(0x6000, 0x7FFF, "raw/ppu-cpu_4200_00.dmp") print("dumping 4200_40.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x40) sb_ram_dump(0x6000, 0x7FFF, "raw/ppu-cpu_4200_40.dmp") print("dumping 4200_80.dmp") dict.nes("NES_CPU_WR", 0x4200, 0x80) sb_ram_dump(0x6000, 0x7FFF, "raw/ppu-cpu_4200_80.dmp") print("dumping 4200_C0.dmp") dict.nes("NES_CPU_WR", 0x4200, 0xC0) sb_ram_dump(0x6000, 0x7FFF, "raw/ppu-cpu_4200_C0.dmp") end local function check_ppu_mirror(a, b) vala = dict.nes("NES_PPU_RD", 0x2800) valb = dict.nes("NES_PPU_RD", 0x2C00) if vala ~= a then print(string.format("value at $2000 changed to %02X! [%02X]", vala, i & 0x3C)) end if valb ~= b then print(string.format("value at $2400 changed to %02X! [%02X]", valb, i & 0x3C)) end end local function sb_mirror_test() dict.nes("NES_PPU_WR", 0x2800, 0xA1) dict.nes("NES_PPU_WR", 0x2C00, 0xA2) print("checking $4200") for i=0xC4,0xFF,4 do --print(string.format(" %02X", i)) dict.nes("NES_CPU_WR", 0x4200, i) vala = dict.nes("NES_PPU_RD", 0x2800) valb = dict.nes("NES_PPU_RD", 0x2C00) if vala ~= 0xA1 then print(string.format("value at $2000 changed to %02X! [%02X]", vala, i & 0x3C)) end if valb ~= 0xA2 then print(string.format("value at $2400 changed to %02X! [%02X]", valb, i & 0x3C)) end end print("checking $4201") for i=0x00,0xF0,0x10 do --print(string.format(" %02X", i)) dict.nes("NES_CPU_WR", 0x4201, i) vala = dict.nes("NES_PPU_RD", 0x2800) valb = dict.nes("NES_PPU_RD", 0x2C00) if vala ~= 0xA1 then print(string.format("value at $2000 changed to %02X! [%02X]", vala, i & 0x3C)) end if valb ~= 0xA2 then print(string.format("value at $2400 changed to %02X! [%02X]", valb, i & 0x3C)) end end print("checking $4202") dict.nes("NES_CPU_WR", 0x4202, 0x40) check_ppu_mirror(0xA1, 0xA2) dict.nes("NES_CPU_WR", 0x4202, 0x08) check_ppu_mirror(0xA1, 0xA2) print("checking $4203") for i=0x00,0xFF,0x01 do --print(string.format(" %02X", i)) dict.nes("NES_CPU_WR", 0x4203, i) vala = dict.nes("NES_PPU_RD", 0x2800) valb = dict.nes("NES_PPU_RD", 0x2C00) if vala ~= 0xA1 then print(string.format("value at $2000 changed to %02X! [%02X]", vala, i & 0x3C)) end if valb ~= 0xA2 then print(string.format("value at $2400 changed to %02X! [%02X]", valb, i & 0x3C)) end end end --Cart should be in reset state upon calling this function --this function processes all user requests for this specific board/mapper local function process(process_opts, console_opts) local test = process_opts["test"] local read = process_opts["read"] local erase = process_opts["erase"] local program = process_opts["program"] local verify = process_opts["verify"] local dumpfile = process_opts["dump_filename"] local flashfile = process_opts["flash_filename"] local verifyfile = process_opts["verify_filename"] local rv = nil local file local prg_size = console_opts["prg_rom_size_kb"] local chr_size = console_opts["chr_rom_size_kb"] local wram_size = console_opts["wram_size_kb"] --initialize device i/o for NES dict.io("IO_RESET") dict.io("NES_INIT") mirror_test(true) --test cart by reading manf/prod ID if test then prgrom_manf_id(true) end --sb_verify_ram() --sb_read_registers() --sb_dump_rom() --sb_write_save() --sb_dump_save() --sb_ppu_test() --sb_ram_full_test() --sb_ppu_high_ram_test() sb_mirror_test() dict.io("IO_RESET") do return end -- Poke some registers init_mapper(debug) dict.nes("NES_CPU_WR", 0x4202, 0x20) --for i=0, 8, 1 do -- dict.nes("NES_CPU_WR", 0x4202, 0x10) -- rv = dict.nes("NES_CPU_RD", 0x4201) -- print(i, "initial read:", string.format("$%02X", rv)) -- while (rv & 0x40 == 0) do -- rv = dict.nes("NES_CPU_RD", 0x4201) -- io.write(".") -- end -- print("") --end --dump the cart to dumpfile read = false if read then print("Attempting to read StudyBox stuff") --initialize the mapper for dumping init_mapper(debug) rv = dict.nes("NES_CPU_RD", 0x4200) print("$4200:", string.format("$%X", rv)) rv = dict.nes("NES_CPU_RD", 0x4201) print("$4201:", string.format("$%X", rv)) rv = dict.nes("NES_CPU_RD", 0x4202) print("$4202:", string.format("$%X", rv)) rv = dict.nes("NES_CPU_RD", 0x4203) print("$4203:", string.format("$%X", rv)) file = assert(io.open(dumpfile, "wb")) --create header: pass open & empty file & rom sizes --create_header(file, prg_size, chr_size) for bank=0, 16, 1 do dict.nes("NES_CPU_WR", 0x4201, bank) print("dumping bank: ", bank) rv = dict.nes("NES_CPU_RD", 0x4201) print("$4201:", string.format("$%X", rv)) --dump cart into file dump.dumptofile( file, prg_size, "186", "PRGROM", true ) end --close file assert(file:close()) end --verify flashfile is on the cart verify = false if verify then --for now let's just dump the file and verify manually --initialize the mapper for dumping init_mapper(debug) file = assert(io.open(verifyfile, "wb")) --dump cart into file dump.dumptofile( file, 512, "A53", "PRGROM", true ) --close file assert(file:close()) end dict.io("IO_RESET") end -- global variables so other modules can use them -- call functions desired to run when script is called/imported -- functions other modules are able to call studybox.process = process -- return the module's table return studybox