Introducing ROM (+ISX) loader
This commit is contained in:
parent
73c8245eff
commit
3dc19b5c9e
|
@ -35,6 +35,8 @@ core {
|
||||||
|
|
||||||
# File dialog
|
# File dialog
|
||||||
dialog {
|
dialog {
|
||||||
|
ext_isx ISX modules (*.isx)
|
||||||
|
ext_vb Virtual Boy ROMs (*.vb)
|
||||||
load Load
|
load Load
|
||||||
load_rom Load ROM
|
load_rom Load ROM
|
||||||
load_rom_error Unable to load the selected ROM file.
|
load_rom_error Unable to load the selected ROM file.
|
||||||
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
package app;
|
||||||
|
|
||||||
|
// Java imports
|
||||||
|
import java.nio.charset.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
// Cartridge ROM module
|
||||||
|
public class ROM {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
private byte[] data; // Binary data
|
||||||
|
private int format; // File format of loaded file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constants //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Formats
|
||||||
|
public static final int RAW = 0;
|
||||||
|
public static final int ISX = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Classes //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// ISX code segments
|
||||||
|
private static class Code {
|
||||||
|
int address; // CPU address
|
||||||
|
int offset; // Position within file
|
||||||
|
int size; // Number of bytes
|
||||||
|
Code(int o, int a, int s) { address = a; offset = o; size = s; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructors //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Default constructor
|
||||||
|
ROM(byte[] data) {
|
||||||
|
|
||||||
|
// Attempt to decode as ISX
|
||||||
|
try { isxDecode(data); return; } catch (Exception e) { }
|
||||||
|
|
||||||
|
// Attempt to decode as raw
|
||||||
|
try { rawDecode(data); return; } catch (Exception e) { }
|
||||||
|
|
||||||
|
// Unable to identify the file contents
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Public Methods //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Retrieve the game code from the ROM header
|
||||||
|
public String getGameCode() {
|
||||||
|
var bytes = new byte[4];
|
||||||
|
System.arraycopy(data, data.length - 517, bytes, 0, 4);
|
||||||
|
return new String(bytes, StandardCharsets.ISO_8859_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the file format of the loaded ROM file
|
||||||
|
public int getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the maker code from the ROM header
|
||||||
|
public String getMaker() {
|
||||||
|
var bytes = new byte[2];
|
||||||
|
System.arraycopy(data, data.length - 519, bytes, 0, 2);
|
||||||
|
return new String(bytes, StandardCharsets.ISO_8859_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the number of bytes in the ROM data
|
||||||
|
public int getSize() {
|
||||||
|
return data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the game title from the ROM header
|
||||||
|
public String getTitle() {
|
||||||
|
return ShiftJIS.decode(data, data.length - 544, 20).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the version number from the ROM header
|
||||||
|
public int getVersion() {
|
||||||
|
return data[data.length - 513] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a byte array containing the ROM contents
|
||||||
|
public byte[] toByteArray() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private Methods //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Decode the file data as ISX
|
||||||
|
private void isxDecode(byte[] data) {
|
||||||
|
var codes = isxParse(data); // ISX code segments
|
||||||
|
int head = -1; // Latest address in bottom half of ROM address space
|
||||||
|
int tail = -1; // Earliest address in upper half of ROM address space
|
||||||
|
|
||||||
|
// Process all code segments
|
||||||
|
for (var code : codes) {
|
||||||
|
int end = code.address + code.size - 1;
|
||||||
|
int start = code.address;
|
||||||
|
boolean isHead = end >= 0; // Lower half of CPU address range
|
||||||
|
boolean isTail = start < 0; // Upper half of CPU address range
|
||||||
|
|
||||||
|
// The segment spans the middle of the ROM address space
|
||||||
|
if (isHead && isTail) {
|
||||||
|
head = 0x7FFFFF;
|
||||||
|
tail = 0x800000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the segment's position within the ROM address space
|
||||||
|
end &= 0x00FFFFFF;
|
||||||
|
start &= 0x00FFFFFF;
|
||||||
|
if (isHead && (head == -1 || head < end )) head = end;
|
||||||
|
if (isTail && (tail == -1 || tail > start)) tail = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the required ROM size
|
||||||
|
int size = 1024;
|
||||||
|
int minSize = (head == -1 ? 0 : head + 1) +
|
||||||
|
(tail == -1 ? 0 : 0x01000000 - tail);
|
||||||
|
if (minSize == 0 || tail == -1 && (minSize - 1 & minSize) != 0)
|
||||||
|
throw new RuntimeException();
|
||||||
|
for (; size < minSize; size <<= 1);
|
||||||
|
|
||||||
|
// Transfer the code segments into the ROM buffer
|
||||||
|
this.data = new byte[size];
|
||||||
|
for (var code : codes)
|
||||||
|
System.arraycopy(
|
||||||
|
data , code.offset,
|
||||||
|
this.data, code.address & size - 1,
|
||||||
|
code.size
|
||||||
|
);
|
||||||
|
|
||||||
|
// The ROM is valid
|
||||||
|
format = ISX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the code records from the ISX data
|
||||||
|
private Code[] isxParse(byte[] data) {
|
||||||
|
int count;
|
||||||
|
int offset = 0;
|
||||||
|
var ret = new ArrayList<Code>();
|
||||||
|
|
||||||
|
// Check for an extended ISX header
|
||||||
|
if (readInt(data, 0, 3) == 0x585349) // ASCII "ISX"
|
||||||
|
offset = 32;
|
||||||
|
|
||||||
|
// Process records
|
||||||
|
while (offset != data.length)
|
||||||
|
switch (data[offset++] & 0xFF) {
|
||||||
|
|
||||||
|
// SNES code
|
||||||
|
case 0x01:
|
||||||
|
if ((data[offset++] & 0xFF) >= 0x80) // Bank
|
||||||
|
offset++; // BankHigh
|
||||||
|
offset += 2; // Address
|
||||||
|
offset += 2 + readInt(data, offset, 2); // Data
|
||||||
|
break;
|
||||||
|
|
||||||
|
// SNES range
|
||||||
|
case 0x03:
|
||||||
|
// Count, { Bank, StartAddress, EndAddress, Type }
|
||||||
|
offset += 2 + readInt(data, offset, 2) * 6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// SNES symbol
|
||||||
|
case 0x04:
|
||||||
|
count = readInt(data, offset, 2); offset += 2;
|
||||||
|
for (; count > 0; count--) // Name, Flags, Bank, Address
|
||||||
|
offset += 5 + readInt(data, offset, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Virtual Boy code
|
||||||
|
case 0x11:
|
||||||
|
int address = readInt(data, offset, 4); offset += 4;
|
||||||
|
int size = readInt(data, offset, 4); offset += 4;
|
||||||
|
if ((address & 0x07000000) != 0x07000000 || size < 0 ||
|
||||||
|
(address & 0x00FFFFFF) + size > 0x01000000)
|
||||||
|
throw new RuntimeException();
|
||||||
|
ret.add(new Code(offset, address, size));
|
||||||
|
offset += size;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Virtual Boy range
|
||||||
|
case 0x13:
|
||||||
|
// Count, { StartAddress, EndAddress, Type }
|
||||||
|
offset += 2 + readInt(data, offset, 2) * 9;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Virtual Boy symbol
|
||||||
|
case 0x14:
|
||||||
|
count = readInt(data, offset, 2); offset += 2;
|
||||||
|
for (; count > 0; count--) // Name, Flags, Address
|
||||||
|
offset += 7 + readInt(data, offset, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// System
|
||||||
|
case 0x20: case 0x21: case 0x22:
|
||||||
|
offset += 4 + readInt(data, offset, 4); // Debug, undocumented
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Invalid record type
|
||||||
|
default:
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret.toArray(new Code[ret.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the file as raw
|
||||||
|
private void rawDecode(byte[] data) {
|
||||||
|
|
||||||
|
// Validate length
|
||||||
|
if (
|
||||||
|
data.length < 1024 || // Exception handlers and ROM header
|
||||||
|
data.length > 0x00FFFFFF || // 24-bit bus width
|
||||||
|
(data.length & data.length - 1) != 0 // Must be a power of 2
|
||||||
|
) throw new RuntimeException();
|
||||||
|
|
||||||
|
// The ROM is valid
|
||||||
|
this.data = data;
|
||||||
|
format = RAW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read an integer from a byte array
|
||||||
|
private static int readInt(byte[] data, int offset, int size) {
|
||||||
|
int ret = 0;
|
||||||
|
for (size--; size >= 0; size--)
|
||||||
|
ret = ret << 8 | data[offset + size] & 0xFF;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -17,6 +17,7 @@ class Window extends JFrame {
|
||||||
private App app; // Containing application
|
private App app; // Containing application
|
||||||
private int number; // Window number within application
|
private int number; // Window number within application
|
||||||
private boolean only; // This is the only application window
|
private boolean only; // This is the only application window
|
||||||
|
private ROM rom; // Currently loaded ROM
|
||||||
private File romFile; // Currently loaded ROM file
|
private File romFile; // Currently loaded ROM file
|
||||||
|
|
||||||
// UI components
|
// UI components
|
||||||
|
@ -146,9 +147,9 @@ class Window extends JFrame {
|
||||||
// Prompt the user to select a file
|
// Prompt the user to select a file
|
||||||
var dlgFile = new JFileChooser(pwd);
|
var dlgFile = new JFileChooser(pwd);
|
||||||
dlgFile.addChoosableFileFilter(new FileNameExtensionFilter(
|
dlgFile.addChoosableFileFilter(new FileNameExtensionFilter(
|
||||||
"Virtual Boy ROMs (*.vb)", "vb"));
|
loc.get("dialog.ext_vb"), "vb"));
|
||||||
dlgFile.addChoosableFileFilter(new FileNameExtensionFilter(
|
dlgFile.addChoosableFileFilter(new FileNameExtensionFilter(
|
||||||
"ISX modules (*.isx)", "isx"));
|
loc.get("dialog.ext_isx"), "isx"));
|
||||||
dlgFile.setAcceptAllFileFilterUsed(true);
|
dlgFile.setAcceptAllFileFilterUsed(true);
|
||||||
dlgFile.setDialogTitle(loc.get("dialog.load_rom"));
|
dlgFile.setDialogTitle(loc.get("dialog.load_rom"));
|
||||||
int option = dlgFile.showDialog(this, loc.get("dialog.load"));
|
int option = dlgFile.showDialog(this, loc.get("dialog.load"));
|
||||||
|
@ -161,7 +162,7 @@ class Window extends JFrame {
|
||||||
// Update the current directory
|
// Update the current directory
|
||||||
pwd = file.getParentFile();
|
pwd = file.getParentFile();
|
||||||
|
|
||||||
// Attempt to load the ROM file
|
// Read the file
|
||||||
var data = Util.fileRead(file);
|
var data = Util.fileRead(file);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
|
@ -172,8 +173,21 @@ class Window extends JFrame {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the ROM
|
||||||
|
ROM rom = null;
|
||||||
|
try { rom = new ROM(data); }
|
||||||
|
catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
loc.get("dialog.load_rom_notvb"),
|
||||||
|
loc.get("dialog.load_rom"),
|
||||||
|
JOptionPane.ERROR_MESSAGE
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the emulation state
|
// Update the emulation state
|
||||||
romFile = file;
|
this.rom = rom;
|
||||||
|
romFile = file;
|
||||||
loc.put(this, "ctrl.filename", file.getName());
|
loc.put(this, "ctrl.filename", file.getName());
|
||||||
updateTitle();
|
updateTitle();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue