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.
199 lines
7.6 KiB
199 lines
7.6 KiB
4 years ago
|
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);
|
||
|
}
|
||
|
}
|