snes-disassembler
Version:
A Super Nintendo (SNES) ROM disassembler for 65816 assembly
409 lines • 14.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BankHandler = void 0;
const cartridge_types_1 = require("./cartridge-types");
/**
* Enhanced bank switching handler with support for all SNES mapping modes
* Handles LoROM, HiROM, ExLoROM, ExHiROM, and special chip configurations
*/
class BankHandler {
constructor(cartridgeInfo) {
this.bankMask = 0x7F;
this.addressMask = 0x7FFF;
this.cartridgeInfo = cartridgeInfo;
this.mappingMode = this.detectMappingMode(cartridgeInfo);
this.initializeMasks();
}
/**
* Detect mapping mode from cartridge info
*/
detectMappingMode(cartridgeInfo) {
switch (cartridgeInfo.type) {
case cartridge_types_1.CartridgeType.HiROM:
return 'HiROM';
case cartridge_types_1.CartridgeType.ExHiROM:
return 'ExHiROM';
case cartridge_types_1.CartridgeType.LoROM:
return 'LoROM';
case cartridge_types_1.CartridgeType.ExLoROM:
return 'ExLoROM';
default:
// Default to LoROM for unknown types
return 'LoROM';
}
}
/**
* Initialize bank and address masks based on mapping mode
*/
initializeMasks() {
switch (this.mappingMode) {
case 'LoROM':
case 'ExLoROM':
this.bankMask = 0x7F; // Banks 00-7F accessible
this.addressMask = 0x7FFF; // 32KB banks (ignore bit 15)
break;
case 'HiROM':
case 'ExHiROM':
this.bankMask = 0xFF; // Banks 00-FF accessible
this.addressMask = 0xFFFF; // 64KB banks (full address)
break;
}
}
/**
* Convert logical address to ROM offset with enhanced bank switching
*/
addressToRomOffset(address) {
const bank = (address >> 16) & 0xFF;
const offset = address & 0xFFFF;
switch (this.mappingMode) {
case 'LoROM':
return this.calculateLoROMOffset(bank, offset);
case 'HiROM':
return this.calculateHiROMOffset(bank, offset);
case 'ExLoROM':
return this.calculateExLoROMOffset(bank, offset);
case 'ExHiROM':
return this.calculateExHiROMOffset(bank, offset);
default:
throw new Error(`Unsupported mapping mode: ${this.mappingMode}`);
}
}
/**
* Convert ROM offset to logical address
*/
romOffsetToAddress(romOffset) {
switch (this.mappingMode) {
case 'LoROM':
return this.calculateLoROMAddress(romOffset);
case 'HiROM':
return this.calculateHiROMAddress(romOffset);
case 'ExLoROM':
return this.calculateExLoROMAddress(romOffset);
case 'ExHiROM':
return this.calculateExHiROMAddress(romOffset);
default:
throw new Error(`Unsupported mapping mode: ${this.mappingMode}`);
}
}
/**
* LoROM mapping calculation (Mode 20)
* Banks 00-7F: ROM at $8000-$FFFF (32KB per bank)
* Banks 80-FF: FastROM mirror
*/
calculateLoROMOffset(bank, offset) {
// FastROM mirror banks (80-FF)
if (bank >= 0x80) {
bank = bank - 0x80;
}
// ROM is only accessible at $8000-$FFFF
if (bank <= 0x7F && offset >= 0x8000) {
return (bank * 0x8000) + (offset - 0x8000);
}
// Handle special system areas
if (bank === 0x00 && offset < 0x8000) {
// System area - not ROM
throw new Error(`System area access: $${offset.toString(16).toUpperCase()}`);
}
throw new Error(`Invalid LoROM address: $${((bank << 16) | offset).toString(16).toUpperCase()}`);
}
/**
* HiROM mapping calculation (Mode 21)
* Banks C0-FF: Direct ROM mapping (64KB per bank)
* Banks 40-7F: Direct ROM mapping (64KB per bank)
*/
calculateHiROMOffset(bank, offset) {
// Banks C0-FF: Direct ROM mapping
if (bank >= 0xC0) {
return ((bank - 0xC0) * 0x10000) + offset;
}
// Banks 40-7F: Direct ROM mapping
if (bank >= 0x40 && bank <= 0x7F) {
return ((bank - 0x40) * 0x10000) + offset;
}
// Banks 80-BF: Mirror of 00-3F at $8000-$FFFF
if (bank >= 0x80 && bank <= 0xBF && offset >= 0x8000) {
return ((bank - 0x80) * 0x8000) + (offset - 0x8000);
}
// Banks 00-3F: ROM at $8000-$FFFF
if (bank <= 0x3F && offset >= 0x8000) {
return (bank * 0x8000) + (offset - 0x8000);
}
// Bank 00: System area
if (bank === 0x00 && offset < 0x8000) {
throw new Error(`System area access: $${offset.toString(16).toUpperCase()}`);
}
throw new Error(`Invalid HiROM address: $${((bank << 16) | offset).toString(16).toUpperCase()}`);
}
/**
* ExLoROM mapping calculation (Mode 25)
* Extended LoROM for ROMs > 2MB
*/
calculateExLoROMOffset(bank, offset) {
// For ExLoROM, we need to handle the extended addressing
// This is similar to LoROM but with additional bank mapping
// Handle FastROM mirror
if (bank >= 0x80) {
bank = bank - 0x80;
}
// Standard LoROM mapping for banks 00-7F
if (bank <= 0x7F && offset >= 0x8000) {
let romOffset = (bank * 0x8000) + (offset - 0x8000);
// Apply bank wrapping for extended ROM sizes
if (this.cartridgeInfo.romSize > 0x200000) { // > 2MB
const bankCount = Math.floor(this.cartridgeInfo.romSize / 0x8000);
romOffset = romOffset % (bankCount * 0x8000);
}
return romOffset;
}
throw new Error(`Invalid ExLoROM address: $${((bank << 16) | offset).toString(16).toUpperCase()}`);
}
/**
* ExHiROM mapping calculation (Mode 25)
* Extended HiROM for ROMs > 4MB
*/
calculateExHiROMOffset(bank, offset) {
// ExHiROM extends the standard HiROM mapping
// Additional banks are mapped in a specific pattern
// Standard HiROM banks
if (bank >= 0xC0) {
return ((bank - 0xC0) * 0x10000) + offset;
}
if (bank >= 0x40 && bank <= 0x7F) {
return ((bank - 0x40) * 0x10000) + offset;
}
// Extended banks for ROMs > 4MB
if (this.cartridgeInfo.romSize > 0x400000) { // > 4MB
// Handle extended bank mapping
if (bank >= 0x80 && bank <= 0xBF && offset >= 0x8000) {
return ((bank - 0x80 + 0x40) * 0x8000) + (offset - 0x8000);
}
}
// Standard HiROM mirrors and system areas
if (bank >= 0x80 && bank <= 0xBF && offset >= 0x8000) {
return ((bank - 0x80) * 0x8000) + (offset - 0x8000);
}
if (bank <= 0x3F && offset >= 0x8000) {
return (bank * 0x8000) + (offset - 0x8000);
}
throw new Error(`Invalid ExHiROM address: $${((bank << 16) | offset).toString(16).toUpperCase()}`);
}
/**
* Calculate LoROM address from ROM offset
*/
calculateLoROMAddress(romOffset) {
const bank = Math.floor(romOffset / 0x8000);
const offset = (romOffset % 0x8000) + 0x8000;
return (bank << 16) | offset;
}
/**
* Calculate HiROM address from ROM offset
*/
calculateHiROMAddress(romOffset) {
// Prefer C0-FF banks for direct mapping
const bank = Math.floor(romOffset / 0x10000) + 0xC0;
const offset = romOffset % 0x10000;
return (bank << 16) | offset;
}
/**
* Calculate ExLoROM address from ROM offset
*/
calculateExLoROMAddress(romOffset) {
// Similar to LoROM but with extended bank handling
const bank = Math.floor(romOffset / 0x8000);
const offset = (romOffset % 0x8000) + 0x8000;
return (bank << 16) | offset;
}
/**
* Calculate ExHiROM address from ROM offset
*/
calculateExHiROMAddress(romOffset) {
// Similar to HiROM but with extended bank handling
const bank = Math.floor(romOffset / 0x10000) + 0xC0;
const offset = romOffset % 0x10000;
return (bank << 16) | offset;
}
/**
* Get valid address ranges for the current mapping mode
*/
getValidAddressRanges() {
const ranges = [];
switch (this.mappingMode) {
case 'LoROM':
case 'ExLoROM':
// System areas
ranges.push({ start: 0x000000, end: 0x007FFF, type: 'SYSTEM' });
// ROM areas
for (let bank = 0; bank <= 0x7F; bank++) {
ranges.push({
start: (bank << 16) | 0x8000,
end: (bank << 16) | 0xFFFF,
type: 'ROM'
});
}
// FastROM mirrors
for (let bank = 0x80; bank <= 0xFF; bank++) {
ranges.push({
start: (bank << 16) | 0x8000,
end: (bank << 16) | 0xFFFF,
type: 'ROM_MIRROR'
});
}
break;
case 'HiROM':
case 'ExHiROM':
// System areas
ranges.push({ start: 0x000000, end: 0x007FFF, type: 'SYSTEM' });
// ROM areas in banks 00-3F at $8000-$FFFF
for (let bank = 0x00; bank <= 0x3F; bank++) {
ranges.push({
start: (bank << 16) | 0x8000,
end: (bank << 16) | 0xFFFF,
type: 'ROM'
});
}
// Direct ROM mapping in banks 40-7F
for (let bank = 0x40; bank <= 0x7F; bank++) {
ranges.push({
start: (bank << 16) | 0x0000,
end: (bank << 16) | 0xFFFF,
type: 'ROM'
});
}
// ROM mirrors in banks 80-BF at $8000-$FFFF
for (let bank = 0x80; bank <= 0xBF; bank++) {
ranges.push({
start: (bank << 16) | 0x8000,
end: (bank << 16) | 0xFFFF,
type: 'ROM_MIRROR'
});
}
// Direct ROM mapping in banks C0-FF
for (let bank = 0xC0; bank <= 0xFF; bank++) {
ranges.push({
start: (bank << 16) | 0x0000,
end: (bank << 16) | 0xFFFF,
type: 'ROM'
});
}
break;
}
return ranges;
}
/**
* Check if an address is valid for the current mapping mode
*/
isValidAddress(address) {
const ranges = this.getValidAddressRanges();
return ranges.some(range => range.type === 'ROM' && address >= range.start && address <= range.end);
}
/**
* Get the mapping mode
*/
getMappingMode() {
return this.mappingMode;
}
/**
* Get bank size for the current mapping mode
*/
getBankSize() {
switch (this.mappingMode) {
case 'LoROM':
case 'ExLoROM':
return 0x8000; // 32KB
case 'HiROM':
case 'ExHiROM':
return 0x10000; // 64KB
default:
return 0x8000;
}
}
/**
* Handle special chip bank switching
* This method can be extended to handle SA-1, SuperFX, etc.
*/
handleSpecialChipBanking(address, chipType) {
if (!chipType) {
chipType = this.cartridgeInfo.specialChip;
}
switch (chipType) {
case 'SA-1 Super Accelerator':
return this.handleSA1Banking(address);
case 'SuperFX Graphics Support Unit':
return this.handleSuperFXBanking(address);
// Add more special chip handlers as needed
default:
return null; // Use standard banking
}
}
/**
* Handle SA-1 specific banking
*/
handleSA1Banking(address) {
// SA-1 has complex banking with multiple memory maps
// This is a simplified implementation
const bank = (address >> 16) & 0xFF;
const offset = address & 0xFFFF;
// SA-1 BW-RAM mapping
if (bank >= 0x00 && bank <= 0x3F && offset >= 0x6000 && offset <= 0x7FFF) {
// BW-RAM area - not ROM
throw new Error(`SA-1 BW-RAM access: $${address.toString(16).toUpperCase()}`);
}
// Use standard banking for ROM areas
return this.addressToRomOffset(address);
}
/**
* Handle SuperFX specific banking
*/
handleSuperFXBanking(address) {
// SuperFX has its own memory mapping
// This is a simplified implementation
const bank = (address >> 16) & 0xFF;
const offset = address & 0xFFFF;
// SuperFX has special RAM areas that aren't ROM
if (bank >= 0x70 && bank <= 0x71) {
// SuperFX RAM - not ROM
throw new Error(`SuperFX RAM access: $${address.toString(16).toUpperCase()}`);
}
// Use standard banking for ROM areas
return this.addressToRomOffset(address);
}
/**
* Get bank information for an address
*/
getBankInfo(address) {
const bank = (address >> 16) & 0xFF;
const offset = address & 0xFFFF;
const ranges = this.getValidAddressRanges();
const range = ranges.find(r => address >= r.start && address <= r.end);
let physicalAddress;
try {
physicalAddress = this.addressToRomOffset(address);
}
catch (error) {
// Address is not mappable to ROM
physicalAddress = undefined;
}
return {
bank,
offset,
type: range?.type || 'INVALID',
physicalAddress
};
}
/**
* Get statistics about the current mapping
*/
getMappingStats() {
const ranges = this.getValidAddressRanges();
const romRanges = ranges.filter(r => r.type === 'ROM');
return {
mode: this.mappingMode,
bankSize: this.getBankSize(),
romSize: this.cartridgeInfo.romSize,
totalBanks: Math.ceil(this.cartridgeInfo.romSize / this.getBankSize()),
validRanges: romRanges.length
};
}
}
exports.BankHandler = BankHandler;
//# sourceMappingURL=bank-handler.js.map