mirror of
https://github.com/ApfelTeeSaft/NESDecompiler.git
synced 2026-08-27 03:43:30 +00:00
194 lines
6.3 KiB
C#
194 lines
6.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using NESDecompiler.Core.Exceptions;
|
|
|
|
namespace NESDecompiler.Core.ROM
|
|
{
|
|
/// <summary>
|
|
/// Handles loading and parsing NES ROM files
|
|
/// </summary>
|
|
public class ROMLoader
|
|
{
|
|
// iNES header constants
|
|
private const int HEADER_SIZE = 16;
|
|
private const int PRG_ROM_SIZE_OFFSET = 4;
|
|
private const int CHR_ROM_SIZE_OFFSET = 5;
|
|
private const int FLAGS_6_OFFSET = 6;
|
|
private const int FLAGS_7_OFFSET = 7;
|
|
private const int PRG_RAM_SIZE_OFFSET = 8;
|
|
private const int FLAGS_9_OFFSET = 9;
|
|
private const int FLAGS_10_OFFSET = 10;
|
|
|
|
// ROM data
|
|
private byte[]? romData;
|
|
private ROMInfo? romInfo;
|
|
|
|
/// <summary>
|
|
/// Information about the loaded ROM
|
|
/// </summary>
|
|
public ROMInfo? ROMInfo => romInfo;
|
|
|
|
/// <summary>
|
|
/// Loads a NES ROM file from disk
|
|
/// </summary>
|
|
/// <param name="filePath">Path to the ROM file</param>
|
|
/// <returns>Information about the loaded ROM</returns>
|
|
public ROMInfo LoadFromFile(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
throw new FileNotFoundException("ROM file not found", filePath);
|
|
}
|
|
|
|
try
|
|
{
|
|
romData = File.ReadAllBytes(filePath);
|
|
return ParseROMHeader();
|
|
}
|
|
catch (Exception ex) when (ex is not ROMException)
|
|
{
|
|
throw new ROMException($"Failed to load ROM file: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a NES ROM from a byte array
|
|
/// </summary>
|
|
/// <param name="data">The ROM data</param>
|
|
/// <returns>Information about the loaded ROM</returns>
|
|
public ROMInfo LoadFromBytes(byte[] data)
|
|
{
|
|
if (data == null || data.Length < HEADER_SIZE)
|
|
{
|
|
throw new ROMException("Invalid ROM data: too short");
|
|
}
|
|
|
|
romData = data;
|
|
return ParseROMHeader();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the iNES ROM header
|
|
/// </summary>
|
|
/// <returns>Information about the ROM</returns>
|
|
private ROMInfo ParseROMHeader()
|
|
{
|
|
// Verify iNES header signature "NES" followed by MS-DOS EOF
|
|
if (romData!.Length < HEADER_SIZE ||
|
|
romData[0] != 0x4E || romData[1] != 0x45 || romData[2] != 0x53 || romData[3] != 0x1A)
|
|
{
|
|
throw new InvalidROMFormatException("Invalid iNES ROM header");
|
|
}
|
|
|
|
romInfo = new ROMInfo
|
|
{
|
|
PRGROMSize = romData[PRG_ROM_SIZE_OFFSET] * 16384, // 16KB units
|
|
CHRROMSize = romData[CHR_ROM_SIZE_OFFSET] * 8192, // 8KB units
|
|
|
|
MapperNumber = (byte)((romData[FLAGS_7_OFFSET] & 0xF0) | ((romData[FLAGS_6_OFFSET] & 0xF0) >> 4)),
|
|
|
|
MirroringType = (romData[FLAGS_6_OFFSET] & 0x01) == 0
|
|
? MirroringType.Horizontal
|
|
: MirroringType.Vertical,
|
|
|
|
HasBatteryBackedRAM = (romData[FLAGS_6_OFFSET] & 0x02) != 0,
|
|
|
|
HasTrainer = (romData[FLAGS_6_OFFSET] & 0x04) != 0,
|
|
|
|
HasFourScreenVRAM = (romData[FLAGS_6_OFFSET] & 0x08) != 0,
|
|
|
|
IsVSSystemCart = (romData[FLAGS_7_OFFSET] & 0x01) != 0,
|
|
|
|
PRGROMOffset = HEADER_SIZE + (((romData[FLAGS_6_OFFSET] & 0x04) != 0) ? 512 : 0),
|
|
|
|
RawData = romData
|
|
};
|
|
|
|
romInfo.CHRROMOffset = romInfo.PRGROMOffset + romInfo.PRGROMSize;
|
|
|
|
var end = romInfo.PRGROMOffset + romInfo.PRGROMSize;
|
|
var nmiOffset = end - 6;
|
|
var resetOffset = end - 4;
|
|
var irqOffset = end - 2;
|
|
|
|
ushort GetVectorAddress(int offset)
|
|
{
|
|
if (offset >= 0 && offset < romData.Length - 1)
|
|
{
|
|
return (ushort)(romData[offset] | (romData[offset + 1] << 8));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Identify entry points (reset vector), NMI handler, and irq handler
|
|
if (romInfo.PRGROMSize > 0)
|
|
{
|
|
// In 6502, reset vector is at 0xFFFC-0xFFFD
|
|
// For NES, this is mapped to the end of the first PRG ROM bank
|
|
romInfo.ResetVector = GetVectorAddress(resetOffset);
|
|
romInfo.NmiVector = GetVectorAddress(nmiOffset);
|
|
romInfo.IrqVector = GetVectorAddress(irqOffset);
|
|
|
|
if (romInfo.NmiVector > 0)
|
|
{
|
|
romInfo.EntryPoints.Add(romInfo.NmiVector);
|
|
}
|
|
|
|
if (romInfo.IrqVector > 0)
|
|
{
|
|
romInfo.EntryPoints.Add(romInfo.IrqVector);
|
|
}
|
|
}
|
|
|
|
return romInfo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a segment of the ROM data
|
|
/// </summary>
|
|
/// <param name="offset">Starting offset</param>
|
|
/// <param name="length">Number of bytes to read</param>
|
|
/// <returns>The requested data segment</returns>
|
|
public byte[] GetROMSegment(int offset, int length)
|
|
{
|
|
if (romData == null)
|
|
throw new ROMException("No ROM data loaded");
|
|
|
|
if (offset < 0 || offset + length > romData.Length)
|
|
throw new ROMException("Requested segment is out of bounds");
|
|
|
|
byte[] segment = new byte[length];
|
|
Array.Copy(romData, offset, segment, 0, length);
|
|
return segment;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the PRG ROM data
|
|
/// </summary>
|
|
/// <returns>The PRG ROM data</returns>
|
|
public byte[] GetPRGROMData()
|
|
{
|
|
if (romInfo == null)
|
|
throw new ROMException("No ROM data loaded");
|
|
|
|
return GetROMSegment(romInfo.PRGROMOffset, romInfo.PRGROMSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the CHR ROM data
|
|
/// </summary>
|
|
/// <returns>The CHR ROM data</returns>
|
|
public byte[] GetCHRROMData()
|
|
{
|
|
if (romInfo == null)
|
|
throw new ROMException("No ROM data loaded");
|
|
|
|
if (romInfo.CHRROMSize == 0)
|
|
return Array.Empty<byte>();
|
|
|
|
return GetROMSegment(romInfo.CHRROMOffset, romInfo.CHRROMSize);
|
|
}
|
|
}
|
|
} |