INL-retro-progdump/host/scripts/snes/v2proto_hirom.lua

1025 lines
30 KiB
Lua

-- create the module's table
local v2proto = {}
-- import required modules
local dict = require "scripts.app.dict"
local dump = require "scripts.app.dump"
local flash = require "scripts.app.flash"
local snes = require "scripts.app.snes"
local apperase = require "scripts.app.erase"
-- file constants
local hirom_name = 'HiROM'
local lorom_name = 'LoROM'
-- Useful References:
-- http://old.smwiki.net/wiki/Internal_ROM_Header
-- https://en.wikibooks.org/wiki/Super_NES_Programming/SNES_memory_map
-- https://patpend.net/technical/snes/sneskart.html
-- https://snesdev.mesen.ca/wiki/index.php
local hardware_type = {
[0x00] = "ROM Only",
[0x01] = "ROM and RAM",
[0x02] = "ROM and Save RAM",
[0x03] = "ROM and DSP1",
[0x13] = "ROM and SuperFX",
[0x15] = "ROM and SuperFX and Save RAM",
[0x1A] = "ROM and SuperFX and Save RAM",
[0x23] = "ROM and OBC1",
[0x33] = "ROM and SA-1",
[0x43] = "ROM and S-DD1",
[0xF3] = "ROM and CX4",
}
--[[
TODO: Investigate these configurations.
4 ROM, RAM and DSP1 chip
5 ROM, Save RAM and DSP1 chip
19 ROM and Super FX chip
227 ROM, RAM and GameBoy data
246 ROM and DSP2 chip
0x001A -> Stunt Race FX
0x00F3 -> Megaman X2, X3
0x0043 -> SF Alpha 2
--]]
-- Upperbound for ROM size, actual program size may be smaller.
local rom_ubound = {
[0x08] = "2 megabits",
[0x09] = "4 megabits",
[0x0A] = "8 megabits",
[0x0B] = "16 megabits",
[0x0C] = "32 megabits",
[0x0D] = "64 megabits",
}
-- Translates size in header to KBytes.
local rom_size_kb_tbl = {
[0x08] = 2 * 128,
[0x09] = 4 * 128,
[0x0A] = 8 * 128,
[0x0B] = 16 * 128,
[0x0C] = 32 * 128,
[0x0D] = 64 * 128,
}
local ram_size_tbl = {
[0x00] = "No sram",
[0x01] = "16 kilobits",
[0x02] = "32 kilobits",
[0x03] = "64 kilobits",
[0x05] = "256 kilobits",
[0x06] = "512 kilobits"
}
-- Translates ram size in header to KBytes.
local ram_size_kb_tbl = {
[0x00] = 0,
[0x01] = 2,
[0x02] = 4,
[0x03] = 8,
[0x05] = 32,
[0x06] = 64
}
local destination_code = {
[0] = "Japan (NTSC)",
[1] = "USA (NTSC)",
[2] = "Australia, Europe, Oceania and Asia (PAL)",
[3] = "Sweden (PAL)",
[4] = "Finland (PAL)",
[5] = "Denmark (PAL)",
[6] = "France (PAL)",
[7] = "Holland (PAL)",
[8] = "Spain (PAL)",
[9] = "Germany, Austria and Switzerland (PAL)",
[10] = "Italy (PAL)",
[11] = "Hong Kong and China (PAL)",
[12] = "Indonesia (PAL)",
[13] = "Korea (PAL)",
}
local developer_code = {
[0x01] = 'Nintendo',
[0x03] = 'Imagineer-Zoom',
[0x05] = 'Zamuse',
[0x06] = 'Falcom',
[0x08] = 'Capcom',
[0x09] = 'HOT-B',
[0x0a] = 'Jaleco',
[0x0b] = 'Coconuts',
[0x0c] = 'Rage Software',
[0x0e] = 'Technos',
[0x0f] = 'Mebio Software',
[0x12] = 'Gremlin Graphics',
[0x13] = 'Electronic Arts',
[0x15] = 'COBRA Team',
[0x16] = 'Human/Field',
[0x17] = 'KOEI',
[0x18] = 'Hudson Soft',
[0x1a] = 'Yanoman',
[0x1c] = 'Tecmo',
[0x1e] = 'Open System',
[0x1f] = 'Virgin Games',
[0x20] = 'KSS',
[0x21] = 'Sunsoft',
[0x22] = 'POW',
[0x23] = 'Micro World',
[0x26] = 'Enix',
[0x27] = 'Loriciel/Electro Brain',
[0x28] = 'Kemco',
[0x29] = 'Seta Co.,Ltd.',
[0x2d] = 'Visit Co.,Ltd.',
[0x31] = 'Carrozzeria',
[0x32] = 'Dynamic',
[0x33] = 'Nintendo',
[0x34] = 'Magifact',
[0x35] = 'Hect',
[0x3c] = 'Empire Software',
[0x3d] = 'Loriciel',
[0x40] = 'Seika Corp.',
[0x41] = 'UBI Soft',
[0x46] = 'System 3',
[0x47] = 'Spectrum Holobyte',
[0x49] = 'Irem',
[0x4b] = 'Raya Systems/Sculptured Software',
[0x4c] = 'Renovation Products',
[0x4d] = 'Malibu Games/Black Pearl',
[0x4f] = 'U.S. Gold',
[0x50] = 'Absolute Entertainment',
[0x51] = 'Acclaim',
[0x52] = 'Activision',
[0x53] = 'American Sammy',
[0x54] = 'GameTek',
[0x55] = 'Hi Tech Expressions',
[0x56] = 'LJN Toys',
[0x5a] = 'Mindscape',
[0x5d] = 'Tradewest',
[0x5f] = 'American Softworks Corp.',
[0x60] = 'Titus',
[0x61] = 'Virgin Interactive Entertainment',
[0x62] = 'Maxis',
[0x67] = 'Ocean',
[0x69] = 'Electronic Arts',
[0x6b] = 'Laser Beam',
[0x6e] = 'Elite',
[0x6f] = 'Electro Brain',
[0x70] = 'Infogrames',
[0x71] = 'Interplay',
[0x72] = 'LucasArts',
[0x73] = 'Parker Brothers',
[0x75] = 'STORM',
[0x78] = 'THQ Software',
[0x79] = 'Accolade Inc.',
[0x7a] = 'Triffix Entertainment',
[0x7c] = 'Microprose',
[0x7f] = 'Kemco',
[0x80] = 'Misawa',
[0x81] = 'Teichio',
[0x82] = 'Namco Ltd.',
[0x83] = 'Lozc',
[0x84] = 'Koei',
[0x86] = 'Tokuma Shoten Intermedia',
[0x88] = 'DATAM-Polystar',
[0x8b] = 'Bullet-Proof Software',
[0x8c] = 'Vic Tokai',
[0x8e] = 'Character Soft',
[0x8f] = 'I\'\'Max',
[0x90] = 'Takara',
[0x91] = 'CHUN Soft',
[0x92] = 'Video System Co., Ltd.',
[0x93] = 'BEC',
[0x95] = 'Varie',
[0x97] = 'Kaneco',
[0x99] = 'Pack in Video',
[0x9a] = 'Nichibutsu',
[0x9b] = 'TECMO',
[0x9c] = 'Imagineer Co.',
[0xa0] = 'Telenet',
[0xa4] = 'Konami',
[0xa5] = 'K.Amusement Leasing Co.',
[0xa7] = 'Takara',
[0xa9] = 'Technos Jap.',
[0xaa] = 'JVC',
[0xac] = 'Toei Animation',
[0xad] = 'Toho',
[0xaf] = 'Namco Ltd.',
[0xb1] = 'ASCII Co. Activison',
[0xb2] = 'BanDai America',
[0xb4] = 'Enix',
[0xb6] = 'Halken',
[0xba] = 'Culture Brain',
[0xbb] = 'Sunsoft',
[0xbc] = 'Toshiba EMI',
[0xbd] = 'Sony Imagesoft',
[0xbf] = 'Sammy',
[0xc0] = 'Taito',
[0xc2] = 'Kemco',
[0xc3] = 'Square',
[0xc4] = 'Tokuma Soft',
[0xc5] = 'Data East',
[0xc6] = 'Tonkin House',
[0xc8] = 'KOEI',
[0xca] = 'Konami USA',
[0xcb] = 'NTVIC',
[0xcd] = 'Meldac',
[0xce] = 'Pony Canyon',
[0xcf] = 'Sotsu Agency/Sunrise',
[0xd0] = 'Disco/Taito',
[0xd1] = 'Sofel',
[0xd2] = 'Quest Corp.',
[0xd3] = 'Sigma',
[0xd6] = 'Naxat',
[0xd8] = 'Capcom Co., Ltd.',
[0xd9] = 'Banpresto',
[0xda] = 'Tomy',
[0xdb] = 'Acclaim',
[0xdd] = 'NCS',
[0xde] = 'Human Entertainment',
[0xdf] = 'Altron',
[0xe0] = 'Jaleco',
[0xe2] = 'Yutaka',
[0xe4] = 'T&ESoft',
[0xe5] = 'EPOCH Co.,Ltd.',
[0xe7] = 'Athena',
[0xe8] = 'Asmik',
[0xe9] = 'Natsume',
[0xea] = 'King Records',
[0xeb] = 'Atlus',
[0xec] = 'Sony Music Entertainment',
[0xee] = 'IGS',
[0xf1] = 'Motown Software',
[0xf2] = 'Left Field Entertainment',
[0xf3] = 'Beam Software',
[0xf4] = 'Tec Magik',
[0xf9] = 'Cybersoft',
[0xff] = 'Hudson Soft',
}
function hexfmt(val)
return string.format("0x%04X", val)
end
local function isempty(s)
return s == nil or s == ""
end
function seq_read(base_addr, n)
local rv = {}
local count = 0
while (count < n) do
local val = dict.snes("SNES_ROM_RD", base_addr + count)
count = count + 1
-- Kind of an ordering hack because Lua likes 1-based structures.
rv[count] = val
end
return rv
end
function string_from_bytes(base_addr, length)
local byte_table = seq_read(base_addr, length)
local s = ""
local count = 0
while (count < length) do
s = s .. string.char(byte_table[count + 1])
count = count + 1
end
return s
end
function word_from_two_bytes(base_addr)
local upper = dict.snes("SNES_ROM_RD", base_addr) << 8
local lower = dict.snes("SNES_ROM_RD", base_addr + 1)
return upper | lower
end
function print_header(internal_header)
local map_mode_str = lorom_name
if (internal_header["map_mode"] & 1) == 1 then map_mode_str = hirom_name end
-- For each field, default to showing hex value so program doesn't crash if a value is unexpected.
local rom_type_str = "UNKNOWN - " .. hexfmt(internal_header["rom_type"])
if hardware_type[internal_header["rom_type"]] then rom_type_str = hardware_type[internal_header["rom_type"]] end
local rom_size_str = "UNKNOWN - " .. hexfmt(internal_header["rom_size"])
if rom_ubound[internal_header["rom_size"]] then rom_size_str = rom_ubound[internal_header["rom_size"]] end
local sram_size_str = "UNKNOWN - " .. hexfmt(internal_header["sram_size"])
if ram_size_tbl[internal_header["sram_size"]] then sram_size_str = ram_size_tbl[internal_header["sram_size"]] end
local exp_size_str = "UNKNOWN - " .. hexfmt(internal_header["exp_ram_size"])
if ram_size_tbl[internal_header["exp_ram_size"]] then exp_size_str = ram_size_tbl[internal_header["exp_ram_size"]] end
local destination_code_str = "UNKNOWN - " .. hexfmt(internal_header["destination_code"])
if destination_code[internal_header["destination_code"]] then
destination_code_str = destination_code[internal_header["destination_code"]]
end
local developer_code_str = "UNKNOWN - " .. hexfmt(internal_header["developer_code"])
if developer_code[internal_header["developer_code"]] then
developer_code_str = developer_code[internal_header["developer_code"]]
end
print("Rom Title:\t\t" .. internal_header["rom_name"])
print("Map Mode:\t\t" .. map_mode_str .. " - " .. hexfmt(internal_header["map_mode"]))
print("Hardware Type:\t\t" .. rom_type_str)
print("Rom Size Upper Bound:\t" .. rom_size_str)
print("SRAM Size:\t\t" .. sram_size_str)
print("Expansion RAM Size:\t" .. exp_size_str)
print("Destination Code:\t" .. destination_code_str)
print("Developer:\t\t" .. developer_code_str)
print("Version:\t\t" .. hexfmt(internal_header["version"]))
print("Checksum:\t\t" .. hexfmt(internal_header["checksum"]))
end
function get_header(map_adjust)
local mapping = "unknown"
-- Rom Registration Addresses (15 bytes)
local addr_maker_code = 0xFFB0 - map_adjust -- 2 bytes
local addr_game_code = 0xFFB2 - map_adjust -- 4 bytes
local addr_fixed_zero = 0xFFB6 - map_adjust -- 7 bytes
local addr_expansion_ram_size = 0xFFBD - map_adjust -- 1 byte
local addr_special_version_code = 0xFFBE - map_adjust -- 1 byte
-- ROM Specification Addresses (32 bytes)
local addr_rom_name = 0xFFC0 - map_adjust -- 21 bytes
local addr_map_mode = 0xFFD5 - map_adjust -- 1 byte
local addr_rom_type = 0xFFD6 - map_adjust -- 1 byte
local addr_rom_size = 0xFFD7 - map_adjust -- 1 byte
local addr_sram_size = 0xFFD8 - map_adjust -- 1 byte
local addr_destination_code = 0xFFD9 - map_adjust -- 1 byte
local addr_developer_code = 0xFFDA - map_adjust -- 1 byte (This is actually manufacturer ID)
local addr_version = 0xFFDB - map_adjust -- 1 byte
local addr_compliment_check = 0xFFDC - map_adjust -- 2 bytes
local addr_checksum = 0xFFDD - map_adjust -- 2 bytes
local internal_header = {
mapping = mapping,
rom_name = string_from_bytes(addr_rom_name, 21),
map_mode = dict.snes("SNES_ROM_RD", addr_map_mode),
rom_type = dict.snes("SNES_ROM_RD", addr_rom_type),
rom_size = dict.snes("SNES_ROM_RD", addr_rom_size),
sram_size = dict.snes("SNES_ROM_RD", addr_sram_size),
exp_ram_size = dict.snes("SNES_ROM_RD", addr_expansion_ram_size),
destination_code = dict.snes("SNES_ROM_RD", addr_destination_code),
developer_code = dict.snes("SNES_ROM_RD", addr_developer_code),
version = dict.snes("SNES_ROM_RD", addr_version),
compliment_check = word_from_two_bytes(addr_compliment_check),
checksum = word_from_two_bytes(addr_checksum)
}
return internal_header
end
function mappingfrommapmode(map_mode_byte)
local is_hirom = (map_mode_byte & 1) > 0
if is_hirom then return hirom_name
else return lorom_name
end
end
function isvalidheader(internal_header)
-- Spot check a few fields.
-- TODO: Check more/all fields, look for printable name?
local valid_rom_type = hardware_type[internal_header["rom_type"]]
local valid_destination_code = destination_code[internal_header["destination_code"]]
return valid_rom_type and internal_header["rom_size"] and internal_header["sram_size"] and valid_destination_code
end
function test()
local hirom_header = get_header(0x0000)
local lorom_header = get_header(0x8000)
local internal_header = nil
if isvalidheader(hirom_header) then
print("Valid header found at HiROM address.")
internal_header = hirom_header
elseif isvalidheader(lorom_header) then
print("Valid header found at LoROM address.")
internal_header = lorom_header
end
if internal_header then
internal_header["mapping"] = mappingfrommapmode(internal_header["map_mode"])
else
print("Could not parse internal ROM header.")
end
return internal_header
end
-- local functions
-- Desc: attempt to read flash rom ID
-- Pre: snes_init() been called to setup i/o
-- Post:Address left on bus memories disabled
-- Rtn: true if proper flash ID found
local function rom_manf_id( debug )
local rv
--enter software mode A11 is highest address bit that needs to be valid
--datasheet not exactly explicit, A11 might not need to be valid
--part has A-1 (negative 1) since it's in byte mode, meaning the part's A11 is actually A12
--WR $AAA:AA $555:55 $AAA:AA
dict.snes("SNES_SET_BANK", 0x00)
dict.snes("SNES_ROM_WR", 0x8AAA, 0xAA)
dict.snes("SNES_ROM_WR", 0x8555, 0x55)
dict.snes("SNES_ROM_WR", 0x8AAA, 0x90)
--read manf ID
local manf_id = dict.snes("SNES_ROM_RD", 0x8000) --0x01 Cypress Manf ID
if debug then print("attempted read SNES ROM manf ID:", string.format("%X", manf_id)) end
--read prod ID
local prod_id = dict.snes("SNES_ROM_RD", 0x8002) --0x7E Prod ID S29GL
if debug then print("attempted read SNES ROM prod ID:", string.format("%X", prod_id)) end
local density_id = dict.snes("SNES_ROM_RD", 0x801C) --density 0x10=8MB 0x1A=4MB
if debug then print("attempted read SNES density ID: ", string.format("%X", density_id)) end
local boot_sect = dict.snes("SNES_ROM_RD", 0x801E) --boot sector 0x00=top 0x01=bottom
if debug then print("attempted read SNES boot sect ID:", string.format("%X", boot_sect)) end
--exit software
dict.snes("SNES_ROM_WR", 0x8000, 0xF0)
--return true if detected flash chip
if (manf_id == 0x01 and prod_id == 0x49) then
print("2MB flash detected")
return true
elseif (manf_id == 0x01 and prod_id == 0x7E) then
print("4-8MB flash detected")
return true
else
return false
end
end
local function erase_flash( debug )
local rv = nil
print("\nErasing TSSOP flash takes about 30sec...");
--WR $AAA:AA $555:55 $AAA:AA
dict.snes("SNES_SET_BANK", 0x00)
dict.snes("SNES_ROM_WR", 0x8AAA, 0xAA)
dict.snes("SNES_ROM_WR", 0x8555, 0x55)
dict.snes("SNES_ROM_WR", 0x8AAA, 0x80)
dict.snes("SNES_ROM_WR", 0x8AAA, 0xAA)
dict.snes("SNES_ROM_WR", 0x8555, 0x55)
dict.snes("SNES_ROM_WR", 0x8AAA, 0x10)
rv = dict.snes("SNES_ROM_RD", 0x8000)
local i = 0
while ( rv ~= 0xFF ) do
rv = dict.snes("SNES_ROM_RD", 0x8000)
i = i + 1
-- if debug then print(" ", i,":", string.format("%x",rv)) end
end
print(i, "naks, done erasing snes.");
--reset flash
dict.snes("SNES_ROM_WR", 0x8000, 0xF0)
end
--dump the SNES ROM starting at the provided bank
--/ROMSEL is always low for this dump
local function dump_rom( file, start_bank, rom_size_KB, mapping, debug )
local KB_per_bank
local addr_base
if (mapping==lorom_name) then
KB_per_bank = 32 -- LOROM has 32KB per bank
addr_base = 0x80 -- $8000 LOROM
elseif (mapping==hirom_name) then
KB_per_bank = 64 -- HIROM has 64KB per bank
addr_base = 0x00 -- $0000 HIROM
else
print("ERROR!! mapping:", mapping, "not supported")
end
local num_reads = rom_size_KB / KB_per_bank
local read_count = 0
while ( read_count < num_reads ) do
if debug then print( "dump ROM part ", read_count, " of ", num_reads) end
if (read_count %8 == 0) then
print("dumping ROM bank: ", read_count, " of ", num_reads-1)
end
--select desired bank
dict.snes("SNES_SET_BANK", start_bank+read_count)
dump.dumptofile( file, KB_per_bank, addr_base, "SNESROM_PAGE", debug )
read_count = read_count + 1
end
end
--dump the SNES RAM starting at the provided bank
--this is currently only for lorom boards where /ROMSEL maps to RAM space
local function dump_ram( file, start_bank, ram_size_KB, mapping, debug )
local KB_per_bank
local addr_base --A15-8 address of ram start
--determine max ram per bank and base address
if (mapping == lorom_name) then
KB_per_bank = 32 -- LOROM has 32KB per bank
addr_base = 0x00 -- $0000 LOROM RAM start address
elseif (mapping == hirom_name) then
KB_per_bank = 8 -- HIROM has 8KB per bank
addr_base = 0x60 -- $6000 HIROM RAM start address
else
print("ERROR! mapping:", mapping, "not supported by dump_ram")
end
local num_banks
--determine how much ram to read per bank
if ram_size_KB == nil then ram_size_KB = 0 end
if (ram_size_KB < KB_per_bank) then
num_banks = 1
KB_per_bank = ram_size_KB
else
num_banks = ram_size_KB / KB_per_bank
end
local read_count = 0
while ( read_count < num_banks ) do
if debug then print( "dump ROM part ", read_count, " of ", num_banks) end
--select desired bank
dict.snes("SNES_SET_BANK", start_bank+read_count)
if (mapping == lorom_name) then --LOROM sram is inside /ROMSEL space
dump.dumptofile( file, KB_per_bank, addr_base, "SNESROM_PAGE", false )
else -- HIROM is outside of /ROMSEL space
dump.dumptofile( file, KB_per_bank, addr_base, "SNESSYS_PAGE", false )
end
read_count = read_count + 1
end
end
--write a single byte to SNES ROM flash
--writes to currently selected bank address
local function wr_flash_byte(addr, value, debug)
if (addr < 0x0000 or addr > 0xFFFF) then
print("\n ERROR! flash write to SNES", string.format("$%X", addr), "must be $0000-FFFF \n\n")
return
end
--send unlock command and write byte
dict.snes("SNES_ROM_WR", 0x8AAA, 0xAA)
dict.snes("SNES_ROM_WR", 0x8555, 0x55)
dict.snes("SNES_ROM_WR", 0x8AAA, 0xA0)
dict.snes("SNES_ROM_WR", addr, value)
local rv = dict.snes("SNES_ROM_RD", addr)
local i = 0
while ( rv ~= value ) do
rv = dict.snes("SNES_ROM_RD", addr)
i = i + 1
end
if debug then print(i, "naks, done writing byte.") end
if debug then print("written value:", string.format("%X",value), "verified value:", string.format("%X",rv)) end
--TODO handle timeout for problems
--TODO return pass/fail/info
end
--fast host flash one bank at a time...
--this is controlled from the host side one bank at a time
--- TODO TODO TODO!!! need to specific first bank!!!! Just like dumping!
local function flash_rom(file, rom_size_KB, mapping, debug)
print("\nProgramming ROM flash")
--test some bytes
-- dict.snes("SNES_SET_BANK", 0x00) wr_flash_byte(0x8000, 0xA5, true) wr_flash_byte(0xFFFF, 0x5A, true)
-- dict.snes("SNES_SET_BANK", 0x01) wr_flash_byte(0x8000, 0x15, true) wr_flash_byte(0xFFFF, 0x1A, true)
--last of 512KB
-- dict.snes("SNES_SET_BANK", 0x0F) wr_flash_byte(0x8000, 0xF5, true) wr_flash_byte(0xFFFF, 0xFA, true)
--most of this is overkill for NROM, but it's how we want to handle things for bigger mappers
local base_addr
local bank_size
local buff_size = 1 --number of bytes to write at a time
local cur_bank = 0
if (mapping==lorom_name) then
base_addr = 0x8000 --writes occur $8000-FFFF
bank_size = 32*1024 --SNES LOROM 32KB per ROM bank
elseif (mapping==hirom_name) then
base_addr = 0x0000 --writes occur $0000-FFFF
bank_size = 64*1024 --SNES HIROM 64KB per ROM bank
else
print("ERROR!! mapping:", mapping, "not supported")
end
local total_banks = rom_size_KB*1024/bank_size
local byte_num --byte number gets reset for each bank
local byte_str, data, readdata
while cur_bank < total_banks do
if (cur_bank %4 == 0) then
print("writting ROM bank: ", cur_bank, " of ", total_banks-1)
end
--select the current bank
if (cur_bank <= 0xFF) then
dict.snes("SNES_SET_BANK", cur_bank)
else
print("\n\nERROR!!!! SNES bank cannot exceed 0xFF, it was:", string.format("0x%X",cur_bank))
return
end
--program the entire bank's worth of data
--[[ This version of the code programs a single byte at a time but doesn't require
-- board specific functions in the firmware
print("This is slow as molasses, but gets the job done")
byte_num = 0 --current byte within the bank
while byte_num < bank_size do
--read next byte from the file and convert to binary
byte_str = file:read(buff_size)
data = string.unpack("B", byte_str, 1)
--write the data
--SLOWEST OPTION: no firmware specific functions 100% host flash algo:
--wr_flash_byte(base_addr+byte_num, data, false) --0.7KBps
--EASIEST FIRMWARE SPEEDUP: 5x faster, create firmware write byte function:
dict.snes("FLASH_WR_3V", base_addr+byte_num, data) --3.8KBps (5.5x faster than above)
--if (verify) then
-- readdata = dict.nes("NES_CPU_RD", base_addr+byte_num)
-- if readdata ~= data then
-- print("ERROR flashing byte number", byte_num, " in bank",cur_bank, " to flash ", data, readdata)
-- end
--end
byte_num = byte_num + 1
end
--]]
--Have the device write a banks worth of data
if (mapping == lorom_name) then
flash.write_file( file, bank_size/1024, "LOROM_3VOLT", "SNESROM", false )
else
flash.write_file( file, bank_size/1024, "HIROM_3VOLT", "SNESROM", false )
end
cur_bank = cur_bank + 1
end
print("Done Programming ROM flash")
end
local function wr_ram(file, first_bank, ram_size_KB, mapping, debug)
print("\nProgramming RAM")
--test some bytes
-- dict.snes("SNES_SET_BANK", 0x00) wr_flash_byte(0x8000, 0xA5, true) wr_flash_byte(0xFFFF, 0x5A, true)
-- dict.snes("SNES_SET_BANK", 0x01) wr_flash_byte(0x8000, 0x15, true) wr_flash_byte(0xFFFF, 0x1A, true)
--last of 512KB
-- dict.snes("SNES_SET_BANK", 0x0F) wr_flash_byte(0x8000, 0xF5, true) wr_flash_byte(0xFFFF, 0xFA, true)
local base_addr
local bank_size
local buff_size = 1 --number of bytes to write at a time
local cur_bank = 0
local total_banks
local byte_num --byte number gets reset for each bank
local byte_str, data, readdata
local addr_base --A15-8 address of ram start
--determine max ram per bank and base address
if (mapping == lorom_name) then
bank_size = 32*1024 -- LOROM has 32KB per bank
base_addr = 0x0000 -- $0000 LOROM RAM start address
elseif (mapping == hirom_name) then
bank_size = 8*1024 -- HIROM has 8KB per bank
base_addr = 0x6000 -- $6000 HIROM RAM start address
else
print("ERROR! mapping:", mapping, "not supported by dump_ram")
end
local num_banks
--determine how much ram to read per bank
if (ram_size_KB*1024 < bank_size) then
total_banks = 1
bank_size = ram_size_KB*1024
else
total_banks = ram_size_KB*1024 / bank_size
end
while cur_bank < total_banks do
print("writting RAM bank: ", cur_bank, " of ", total_banks-1)
--select the current bank
if (cur_bank <= 0xFF) then
dict.snes("SNES_SET_BANK", cur_bank+first_bank)
else
print("\n\nERROR!!!! SNES bank cannot exceed 0xFF, it was:", string.format("0x%X",cur_bank))
return
end
--program the entire bank's worth of data
---[[ This version of the code programs a single byte at a time but doesn't require
-- board specific functions in the firmware
print("This is slow as molasses, but gets the job done")
byte_num = 0 --current byte within the bank
while byte_num < bank_size do
--read next byte from the file and convert to binary
byte_str = file:read(buff_size)
data = string.unpack("B", byte_str, 1)
--write the data
--SLOWEST OPTION: no firmware specific functions 100% host flash algo:
--wr_flash_byte(base_addr+byte_num, data, false) --0.7KBps
--EASIEST FIRMWARE SPEEDUP: 5x faster, create firmware write byte function:
--dict.snes("FLASH_WR_3V", base_addr+byte_num, data) --3.8KBps (5.5x faster than above)
if (mapping == lorom_name) then
dict.snes("SNES_ROM_WR", base_addr+byte_num, data) --3.8KBps (5.5x faster than above)
else
dict.snes("SNES_SYS_WR", base_addr+byte_num, data) --3.8KBps (5.5x faster than above)
end
--if (verify) then
-- readdata = dict.nes("NES_CPU_RD", base_addr+byte_num)
-- if readdata ~= data then
-- print("ERROR flashing byte number", byte_num, " in bank",cur_bank, " to flash ", data, readdata)
-- end
--end
byte_num = byte_num + 1
end
--]]
--Have the device write a banks worth of data
--flash.write_file( file, bank_size/1024, "LOROM_3VOLT", "SNESROM", false )
cur_bank = cur_bank + 1
end
print("Done Programming ROM flash")
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 rv = nil
local file
--initialize device i/o for SNES
dict.io("IO_RESET")
dict.io("SNES_INIT")
local internal_header = nil
-- Use specified mapper if provided, otherwise autodetect.
local snes_mapping = console_opts["mapper"]
if (snes_mapping == lorom_name) then
-- LOROM typically sees the upper half (A15=1) of the first address 0b0000:1000_0000
rombank = 0x00
rambank = 0x70 --LOROM maps from 0x70 to 0x7D
--some for lower half of bank only, some for both halfs...
elseif (snes_mapping == hirom_name) then
-- HIROM typically sees the last 4MByte as the first addresses = 0b1100:0000_0000
rombank = 0xC0
--rombank = 0x40 --second HiROM bank (slow)
rambank = 0x30
end
local dumpram = process_opts["dumpram"]
local ramdumpfile = process_opts["dumpram_filename"]
-- Use specified ram size if provided, otherwise autodetect.
local ram_size = console_opts["wram_size_kb"]
-- Use specified rom size if provided, otherwise autodetect.
local rom_size = console_opts["rom_size_kbyte"]
-- TODO: Put this in a function.
-- SNES memory map banking
-- A15 always high for LOROM (A22 is typically low too)
-- A22 always high for HIROM
-- A23 splits the map in half
-- A22 splits it in quarters (between what's typically low half and high half)
-- b 7 6 5 4 : 3 2 1 0
-- A23 22 21 20 : 19 18 17 16
local rombank --first bank of rom byte that contains A23-16
local rambank --first bank of ram
--test cart by reading manf/prod ID
if test then
print("Testing SNES board");
internal_header = test()
print_header(internal_header)
-- Autodetect any missing parameters.
if isempty(snes_mapping) then
snes_mapping = internal_header["mapping"]
print("Mapping not provided, " .. snes_mapping .. " detected.")
if (snes_mapping == lorom_name) then
-- LOROM typically sees the upper half (A15=1) of the first address 0b0000:1000_0000
rombank = 0x00
rambank = 0x70 --LOROM maps from 0x70 to 0x7D
--some for lower half of bank only, some for both halfs...
elseif (snes_mapping == hirom_name) then
-- HIROM typically sees the last 4MByte as the first addresses = 0b1100:0000_0000
rombank = 0xC0
--rombank = 0x40 --second HiROM bank (slow)
rambank = 0x30
end
end
-- Autodetect missing ram size
if (ram_size == 0) or (ram_size == nil) then
sram_table = ram_size_kb_tbl[internal_header["sram_size"]]
exp_ram_table = ram_size_kb_tbl[internal_header["exp_ram_size"]]
-- Some titles (Yoshi's Story, Stunt Race FX) use expansion ram header value
-- ram_size will use sram value unless a valid size is found in expansion ram header
if (sram_table == 0) or (sram_table == nil) then
if exp_ram_table == nil then
ram_size = sram_table
else
ram_size = exp_ram_table
end
else
ram_size = sram_table
end
assert(ram_size, "Save RAM Size unknown and not provided, please add ram size to console_opts")
print("Save RAM Size not provided, " .. ram_size .. " detected.")
end
if (rom_size == 0) or (rom_size == nil) then
rom_size = rom_size_kb_tbl[internal_header["rom_size"]]
assert(rom_size, "ROM Size unknown and not provided, please add rom size to console_opts")
print("ROM Size not provided, " .. rom_ubound[internal_header["rom_size"]] .. " detected.")
end
--[[SNES detect if able to read flash ID's
if not rom_manf_id(true) then
print("ERROR unable to read flash ID")
return
end
--]]
end
--dump the ram to file
if dumpram then
print("\nDumping SAVE RAM...")
--may have to verify /RESET is high to enable SRAM
file = assert(io.open(ramdumpfile, "wb"))
--dump cart into file
dump_ram(file, rambank, ram_size, snes_mapping, true)
--may disable SRAM by placing /RESET low
--close file
assert(file:close())
print("DONE Dumping SAVE RAM")
end
--dump the cart to dumpfile
if process_opts["read"] then
print("\nDumping SNES ROM...")
file = assert(io.open(process_opts["dump_filename"], "wb"))
--dump cart into file
dump_rom(file, rombank, rom_size, snes_mapping, false)
--close file
assert(file:close())
print("DONE Dumping SNES ROM")
end
--erase the cart
if process_opts["erase"] then
erase_flash()
end
--write to wram on the cart
if process_opts["writeram"] then
print("\nWritting to SAVE RAM...")
file = assert(io.open(process_opts["writeram_filename"], "rb"))
--flash.write_file( file, ram_size, "NOVAR", "PRGRAM", false )
--flash.write_file( file, ram_size, "LOROM_3VOLT", "SNESROM", false )
wr_ram(file, rambank, ram_size, snes_mapping, true)
--close file
assert(file:close())
print("DONE Writting SAVE RAM")
end
--program flashfile to the cart
if process_opts["program"] then
--open file
file = assert(io.open(flashfile, "rb"))
--determine if auto-doubling, deinterleaving, etc,
--needs done to make board compatible with rom
--flash cart
flash_rom(file, rom_size, snes_mapping, true)
--close file
assert(file:close())
end
--verify flashfile is on the cart
if process_opts["verify"] then
print("\nPost dumping SNES ROM...")
--for now let's just dump the file and verify manually
file = assert(io.open(verifyfile, "wb"))
--dump cart into file
dump_rom(file, rombank, rom_size, snes_mapping, false)
--close file
assert(file:close())
print("DONE Post dumping SNES ROM")
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
v2proto.process = process
-- return the module's table
return v2proto