Displays most of the header data of GameBoy ROM dumps.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
7.6 KiB

using System;
using System.IO;
using System.Linq;
using System.Text;
namespace DMGHeader {
public class DmgHeader {
public DmgEntryPoint EntryPoint { get; set; }
public bool ValidLogo { get; set; }
public string Title { get; set; }
public CgbMode CgbMode { get; set; }
public bool SgbSupport { get; set; }
public string CartridgeType { get; set; }
public MemorySize RomSize { get; set; }
public MemorySize ExternalRamSize { get; set; }
public DestinationCode DestinationCode { get; set; }
public byte MaskRomVersion { get; set; }
public byte HeaderChecksum { get; set; }
public bool ValidHeaderChecksum { get; set; }
public UInt16 CartridgeChecksum { get; set; }
private byte[] _validLogo = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
};
public DmgHeader(string filename) {
using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
using var br = new BinaryReader(fs);
fs.Seek(0x100, SeekOrigin.Begin);
EntryPoint = ReadEntryPoint(br.ReadBytes(4));
ValidLogo = ValidateLogo(br.ReadBytes(_validLogo.Length));
// See if the upper bit of the CGB flag is set to see how much of the title field to read.
fs.Seek(0x143, SeekOrigin.Begin);
var cgb = (br.ReadByte() & 0b10000000) != 0;
fs.Seek(0x134, SeekOrigin.Begin);
var titleLen = cgb ? 15 : 16;
Title = ReadTitle(br.ReadBytes(titleLen));
CgbMode = cgb ? ReadCgbMode(br.ReadByte()) : CgbMode.Disabled;
fs.Seek(0x146, SeekOrigin.Begin);
SgbSupport = br.ReadByte() == 0x03;
CartridgeType = ReadCartridgeType(br.ReadByte());
RomSize = ReadRomSize(br.ReadByte());
ExternalRamSize = ReadExternalRamSize(br.ReadByte());
DestinationCode = ReadDestinationCode(br.ReadByte());
fs.Seek(0x14C, SeekOrigin.Begin);
MaskRomVersion = br.ReadByte();
HeaderChecksum = br.ReadByte();
fs.Seek(0x134, SeekOrigin.Begin);
ValidHeaderChecksum = HeaderChecksum == CalculateHeaderChecksum(br.ReadBytes(0x19));
fs.Seek(0x14E, SeekOrigin.Begin);
CartridgeChecksum = br.ReadUInt16();
}
private byte CalculateHeaderChecksum(byte[] data) {
byte x = 0;
unchecked {
x = data.Aggregate(x, (current, b) => (byte)(current - b - 1));
}
return x;
}
private string ReadCartridgeType(byte type) {
switch(type) {
case 0x00: return "ROM ONLY";
case 0x01: return "MBC1";
case 0x02: return "MBC1+RAM";
case 0x03: return "MBC1+RAM+BATTERY";
case 0x05: return "MBC2";
case 0x06: return "MBC2+BATTERY";
case 0x08: return "ROM+RAM";
case 0x09: return "ROM+RAM+BATTERY";
case 0x0B: return "MMM01";
case 0x0C: return "MMM01+RAM";
case 0x0D: return "MMM01+RAM+BATTERY";
case 0x0F: return "MBC3+TIMER+BATTERY";
case 0x10: return "MBC3+TIMER+RAM+BATTERY";
case 0x11: return "MBC3";
case 0x12: return "MBC3+RAM";
case 0x13: return "MBC3+RAM+BATTERY";
case 0x19: return "MBC5";
case 0x1A: return "MBC5+RAM";
case 0x1B: return "MBC5+RAM+BATTERY";
case 0x1C: return "MBC5+RUMBLE";
case 0x1D: return "MBC5+RUMBLE+RAM";
case 0x1E: return "MBC5+RUMBLE+RAM+BATTERY";
case 0x20: return "MBC6";
case 0x22: return "MBC7+SENSOR+RUMBLE+RAM+BATTERY";
case 0xFC: return "POCKET CAMERA";
case 0xFD: return "BANDAI TAMA5";
case 0xFE: return "HuC3";
case 0xFF: return "HuC1+RAM+BATTERY";
default: return "Unknown";
}
}
private CgbMode ReadCgbMode(byte mode) {
if((mode & 0b10000000) == 0) return CgbMode.Disabled;
if((mode & 0b01000000) != 0) return CgbMode.Required;
if((mode & 0b00001100) != 0) return CgbMode.PaletteOnly;
return CgbMode.Enhanced;
}
private DestinationCode ReadDestinationCode(byte data) {
switch(data) {
case 0x00: return DestinationCode.Japanese;
case 0x01: return DestinationCode.NonJapanese;
default: return DestinationCode.Unknown;
}
}
private DmgEntryPoint ReadEntryPoint(byte[] data) {
var entry = new DmgEntryPoint();
var i = 0;
for(; i < data.Length; i++) {
if(data[i] != 0xC3 && data[i] != 0x18) continue;
entry.Jump = data[i] == 0xC3 ? JumpType.Absolute : JumpType.Relative;
break;
}
if(entry.Jump == JumpType.None) return entry;
var addrLen = entry.Jump == JumpType.Absolute ? 2 : 1;
var addr = data.Skip(++i).Take(addrLen).ToArray();
if(addr.Length != addrLen) return entry;
if(entry.Jump == JumpType.Absolute) {
// Stitch the values into a 16-bit value.
entry.JumpAddress = addr[1] << 8 | addr[0];
} else {
entry.JumpAddress = addr[0] + 0x100 + i;
}
return entry;
}
private MemorySize ReadExternalRamSize(byte data) {
var size = new MemorySize(8);
switch(data) {
case 0x00: size.Size = 0; return size;
case 0x01: size.Size = 2; return size;
case 0x02: size.Size = 8; return size;
case 0x03: size.Size = 32; return size;
case 0x04: size.Size = 128; return size;
case 0x05: size.Size = 64; return size;
default: return null;
}
}
private MemorySize ReadRomSize(byte data) {
var size = new MemorySize(16);
switch(data) {
case 0x00: size.Size = 32; return size;
case 0x01: size.Size = 64; return size;
case 0x02: size.Size = 128; return size;
case 0x03: size.Size = 256; return size;
case 0x04: size.Size = 512; return size;
case 0x05: size.Size = 1024; return size;
case 0x06: size.Size = 2048; return size;
case 0x07: size.Size = 4096; return size;
case 0x08: size.Size = 8192; return size;
case 0x52: size.Size = 1152; return size;
case 0x53: size.Size = 1280; return size;
case 0x54: size.Size = 1536; return size;
default: return null;
}
}
private string ReadTitle(byte[] data) {
// Convert nulls to spaces.
for(var i = 0; i < data.Length; i++) {
if(data[i] == 0) data[i] = 32;
}
return Encoding.ASCII.GetString(data).Trim();
}
private bool ValidateLogo(byte[] data) =>
data != null && data.Length == _validLogo.Length && data.SequenceEqual(_validLogo);
}
}