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
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); |
|
} |
|
}
|
|
|