using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using System.Text; using Aaru.CommonTypes; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Attributes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Helpers; using Aaru.Logging; using Marshal = Aaru.Helpers.Marshal; namespace Aaru.Images; [SuppressMessage("ReSharper", "UnusedType.Global")] public partial class GameBoy : IByteAddressableImage { byte[] _data; Stream _dataStream; ImageInfo _imageInfo; bool _opened; #region IByteAddressableImage Members /// public string Author => Authors.NataliaPortillo; /// public Metadata AaruMetadata => null; /// public List DumpHardware => null; /// public string Format => "Nintendo Game Boy cartridge dump"; /// public Guid Id => new("04AFDB93-587E-413B-9B52-10D4A92966CF"); /// // ReSharper disable once ConvertToAutoProperty public ImageInfo Info => _imageInfo; /// public string Name => Localization.GameBoy_Name; /// public bool Identify(IFilter imageFilter) { if(imageFilter == null) return false; Stream stream = imageFilter.GetDataForkStream(); // Not sure but seems to be a multiple of at least this if(stream.Length % 32768 != 0) return false; stream.Position = 0x104; var magicBytes = new byte[8]; stream.EnsureRead(magicBytes, 0, 8); var magic = BitConverter.ToUInt64(magicBytes, 0); return magic == 0x0B000DCC6666EDCE; } /// public ErrorNumber Open(IFilter imageFilter) { if(imageFilter == null) return ErrorNumber.NoSuchFile; Stream stream = imageFilter.GetDataForkStream(); // Not sure but seems to be a multiple of at least this, maybe more if(stream.Length % 512 != 0) return ErrorNumber.InvalidArgument; stream.Position = 0x104; var magicBytes = new byte[8]; stream.EnsureRead(magicBytes, 0, 8); var magic = BitConverter.ToUInt64(magicBytes, 0); if(magic != 0x0B000DCC6666EDCE) return ErrorNumber.InvalidArgument; _data = new byte[imageFilter.DataForkLength]; stream.Position = 0; stream.EnsureRead(_data, 0, (int)imageFilter.DataForkLength); _imageInfo = new ImageInfo { Application = "Multi Game Doctor 2", CreationTime = imageFilter.CreationTime, ImageSize = (ulong)imageFilter.DataForkLength, MediaType = MediaType.GameBoyGamePak, LastModificationTime = imageFilter.LastWriteTime, Sectors = (ulong)imageFilter.DataForkLength, MetadataMediaType = MetadataMediaType.LinearMedia }; Header header = Marshal.ByteArrayToStructureBigEndian
(_data, 0x100, Marshal.SizeOf
()); var name = new byte[(header.Name[^1] & 0x80) == 0x80 ? 15 : 16]; Array.Copy(header.Name, 0, name, 0, name.Length); _imageInfo.MediaTitle = StringHandlers.CToString(name); var sb = new StringBuilder(); sb.AppendFormat(Localization.Name_0, _imageInfo.MediaTitle).AppendLine(); if((header.Name[^1] & 0xC0) == 0xC0) sb.AppendLine(Localization.Requires_Game_Boy_Color); else { if((header.Name[^1] & 0xC0) == 0xC0) sb.AppendLine(Localization.Contains_features_for_Game_Boy_Color); if(header.Sgb == 0x03) sb.AppendLine(Localization.Contains_features_for_Super_Game_Boy); } sb.AppendFormat(Localization.Region_0, header.Country == 0 ? Localization.Japan : Localization.World) .AppendLine(); sb.AppendFormat(Localization.Cartridge_type_0, DecodeCartridgeType(header.RomType)).AppendLine(); sb.AppendFormat(Localization.ROM_size_0_bytes, DecodeRomSize(header.RomSize)).AppendLine(); if(header.SramSize > 0) sb.AppendFormat(Localization.Save_RAM_size_0_bytes, DecodeSaveRamSize(header.SramSize)).AppendLine(); sb.AppendFormat(Localization.Licensee_0, DecodeLicensee(header.Licensee, header.LicenseeNew)).AppendLine(); sb.AppendFormat(Localization.Revision_0, header.Revision).AppendLine(); sb.AppendFormat(Localization.Header_checksum_0, header.HeaderChecksum).AppendLine(); sb.AppendFormat(Localization.Checksum_0_X4, header.Checksum).AppendLine(); _imageInfo.Comments = sb.ToString(); _opened = true; return ErrorNumber.NoError; } /// public string ErrorMessage { get; private set; } /// public bool IsWriting { get; private set; } /// public IEnumerable KnownExtensions => [".gb", ".gbc", ".sgb"]; /// public IEnumerable SupportedMediaTags => []; /// public IEnumerable SupportedMediaTypes => [MediaType.GameBoyGamePak]; /// public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions => []; /// public IEnumerable SupportedSectorTags => []; /// public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, uint negativeSectors, uint overflowSectors, uint sectorSize) => Create(path, mediaType, options, (long)sectors) == ErrorNumber.NoError; /// public bool Close() { if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return false; } if(!IsWriting) { ErrorMessage = Localization.Image_is_not_opened_for_writing; return false; } _dataStream.Position = 0; _dataStream.Write(_data, 0, _data.Length); _dataStream.Close(); IsWriting = false; _opened = false; return true; } /// public bool SetMetadata(Metadata metadata) => false; /// public bool SetDumpHardware(List dumpHardware) => false; /// public bool SetImageInfo(ImageInfo imageInfo) => true; /// public long Position { get; set; } /// public ErrorNumber Create(string path, MediaType mediaType, Dictionary options, long maximumSize) { if(_opened) { ErrorMessage = Localization.Cannot_create_an_opened_image; return ErrorNumber.InvalidArgument; } if(mediaType != MediaType.GameBoyGamePak) { ErrorMessage = string.Format(Localization.Unsupported_media_format_0, mediaType); return ErrorNumber.NotSupported; } _imageInfo = new ImageInfo { MediaType = mediaType, Sectors = (ulong)maximumSize }; try { _dataStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } catch(IOException ex) { ErrorMessage = string.Format(Localization.Could_not_create_new_image_file_exception_0, ex.Message); AaruLogging.Exception(ex, Localization.Could_not_create_new_image_file_exception_0, ex.Message); return ErrorNumber.InOutError; } _imageInfo.MediaType = mediaType; IsWriting = true; _opened = true; _data = new byte[maximumSize]; return ErrorNumber.NoError; } /// public ErrorNumber GetMappings(out LinearMemoryMap mappings) { mappings = new LinearMemoryMap(); if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } Header header = Marshal.ByteArrayToStructureBigEndian
(_data, 0x100, Marshal.SizeOf
()); var hasMapper = false; var hasSaveRam = false; string mapperManufacturer = null; string mapperName = null; switch(header.RomType) { case 0x01: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC1"; break; case 0x02: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC1"; hasSaveRam = true; break; case 0x03: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC1"; hasSaveRam = true; break; case 0x05: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC2"; break; case 0x06: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC2"; break; case 0x08: hasSaveRam = true; break; case 0x09: hasSaveRam = true; break; case 0x0B: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MMM01"; break; case 0x0C: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MMM01"; hasSaveRam = true; break; case 0x0D: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MMM01"; hasSaveRam = true; break; case 0x0F: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC3"; break; case 0x10: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC3"; hasSaveRam = true; break; case 0x11: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC3"; break; case 0x12: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC3"; hasSaveRam = true; break; case 0x13: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC3"; hasSaveRam = true; break; case 0x19: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; break; case 0x1A: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; hasSaveRam = true; break; case 0x1B: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; hasSaveRam = true; break; case 0x1C: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; break; case 0x1D: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; hasSaveRam = true; break; case 0x1E: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC5"; hasSaveRam = true; break; case 0x20: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC6"; break; case 0x22: hasMapper = true; mapperManufacturer = "Nintendo"; mapperName = "MBC7"; hasSaveRam = true; break; case 0xFC: // Pocket Camera mappings = new LinearMemoryMap { Devices = [ new LinearMemoryDevice { Location = "U1", Manufacturer = "Nintendo", Model = "MAC-GBD", Package = "LQFP-100", Type = LinearMemoryType.Processor }, new LinearMemoryDevice { Location = "U2", Manufacturer = "Nintendo", Model = "GBD-PCAX-0", Package = "TSOP-32", Type = LinearMemoryType.ROM, PhysicalAddress = new LinearMemoryAddressing { Start = 0, Length = DecodeRomSize(header.RomSize) } }, new LinearMemoryDevice { Location = "U3", Manufacturer = "Sharp", Model = "52CV1000SF85LL", Package = "TSOP-32", Type = LinearMemoryType.SaveRAM, PhysicalAddress = new LinearMemoryAddressing { Start = DecodeRomSize(header.RomSize), Length = DecodeSaveRamSize(header.SramSize) } } ] }; return ErrorNumber.NoError; case 0xFD: hasMapper = true; mapperManufacturer = "Bandai"; mapperName = "TAMA5"; break; case 0xFE: hasMapper = true; mapperManufacturer = "Hudson"; mapperName = "HuC-3"; break; case 0xFF: hasMapper = true; mapperManufacturer = "Hudson"; mapperName = "HuC-1"; break; } mappings = new LinearMemoryMap(); if(header.SramSize > 0) hasSaveRam = true; var devices = 1; if(hasSaveRam) devices++; if(hasMapper) devices++; mappings.Devices = new LinearMemoryDevice[devices]; mappings.Devices[0].Type = LinearMemoryType.ROM; mappings.Devices[0].PhysicalAddress = new LinearMemoryAddressing { Start = 0, Length = DecodeRomSize(header.RomSize) }; if(hasSaveRam) { mappings.Devices[1] = new LinearMemoryDevice { Type = LinearMemoryType.SaveRAM, PhysicalAddress = new LinearMemoryAddressing { Start = mappings.Devices[0].PhysicalAddress.Length, Length = DecodeSaveRamSize(header.SramSize) } }; } if(hasMapper) { mappings.Devices[^1] = new LinearMemoryDevice { Type = LinearMemoryType.Mapper, Manufacturer = mapperManufacturer, Model = mapperName }; } return ErrorNumber.NoError; } /// public ErrorNumber ReadByte(out byte b, bool advance = true) => ReadByteAt(Position, out b, advance); /// public ErrorNumber ReadByteAt(long position, out byte b, bool advance = true) { b = 0; if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } if(position >= _data.Length) { ErrorMessage = Localization.The_requested_position_is_out_of_range; return ErrorNumber.OutOfRange; } b = _data[position]; if(advance) Position = position + 1; return ErrorNumber.NoError; } /// public ErrorNumber ReadBytes(byte[] buffer, int offset, int bytesToRead, out int bytesRead, bool advance = true) => ReadBytesAt(Position, buffer, offset, bytesToRead, out bytesRead, advance); /// public ErrorNumber ReadBytesAt(long position, byte[] buffer, int offset, int bytesToRead, out int bytesRead, bool advance = true) { bytesRead = 0; if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } if(position >= _data.Length) { ErrorMessage = Localization.The_requested_position_is_out_of_range; return ErrorNumber.OutOfRange; } if(buffer is null) { ErrorMessage = Localization.Buffer_must_not_be_null; return ErrorNumber.InvalidArgument; } if(offset + bytesToRead > buffer.Length) bytesRead = buffer.Length - offset; if(position + bytesToRead > _data.Length) bytesToRead = (int)(_data.Length - position); Array.Copy(_data, position, buffer, offset, bytesToRead); if(advance) Position = position + bytesToRead; bytesRead = bytesToRead; return ErrorNumber.NoError; } /// public ErrorNumber SetMappings(LinearMemoryMap mappings) { if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } if(!IsWriting) { ErrorMessage = Localization.Image_is_not_opened_for_writing; return ErrorNumber.ReadOnly; } var foundRom = false; var foundSaveRam = false; var foundMapper = false; // Sanitize foreach(LinearMemoryDevice map in mappings.Devices) { switch(map.Type) { case LinearMemoryType.ROM when !foundRom: foundRom = true; break; case LinearMemoryType.SaveRAM when !foundSaveRam: foundSaveRam = true; break; case LinearMemoryType.Mapper when !foundMapper: foundMapper = true; break; default: return ErrorNumber.InvalidArgument; } } // Cannot save in this image format anyway return foundRom ? ErrorNumber.NoError : ErrorNumber.InvalidArgument; } /// public ErrorNumber WriteByte(byte b, bool advance = true) => WriteByteAt(Position, b, advance); /// public ErrorNumber WriteByteAt(long position, byte b, bool advance = true) { if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } if(!IsWriting) { ErrorMessage = Localization.Image_is_not_opened_for_writing; return ErrorNumber.ReadOnly; } if(position >= _data.Length) { ErrorMessage = Localization.The_requested_position_is_out_of_range; return ErrorNumber.OutOfRange; } _data[position] = b; if(advance) Position = position + 1; return ErrorNumber.NoError; } /// public ErrorNumber WriteBytes(byte[] buffer, int offset, int bytesToWrite, out int bytesWritten, bool advance = true) => WriteBytesAt(Position, buffer, offset, bytesToWrite, out bytesWritten, advance); /// public ErrorNumber WriteBytesAt(long position, byte[] buffer, int offset, int bytesToWrite, out int bytesWritten, bool advance = true) { bytesWritten = 0; if(!_opened) { ErrorMessage = Localization.No_image_has_been_opened; return ErrorNumber.NotOpened; } if(!IsWriting) { ErrorMessage = Localization.Image_is_not_opened_for_writing; return ErrorNumber.ReadOnly; } if(position >= _data.Length) { ErrorMessage = Localization.The_requested_position_is_out_of_range; return ErrorNumber.OutOfRange; } if(buffer is null) { ErrorMessage = Localization.Buffer_must_not_be_null; return ErrorNumber.InvalidArgument; } if(offset + bytesToWrite > buffer.Length) bytesToWrite = buffer.Length - offset; if(position + bytesToWrite > _data.Length) bytesToWrite = (int)(_data.Length - position); Array.Copy(buffer, offset, _data, position, bytesToWrite); if(advance) Position = position + bytesToWrite; bytesWritten = bytesToWrite; return ErrorNumber.NoError; } #endregion [SuppressMessage("ReSharper", "StringLiteralTypo")] static string DecodeLicensee(byte headerLicensee, byte[] headerLicenseeNew) { if(headerLicensee != 0x33) { return headerLicensee switch { 0x00 => Localization.none_licensee, 0x01 => "nintendo", 0x08 => "capcom", 0x09 => "hot-b", 0x0A => "jaleco", 0x0B => "coconuts", 0x0C => "elite systems", 0x13 => "electronic arts", 0x18 => "hudsonsoft", 0x19 => "itc entertainment", 0x1A => "yanoman", 0x1D => "clary", 0x1F => "virgin", 0x20 => "KSS", 0x24 => "pcm complete", 0x25 => "san-x", 0x28 => "kotobuki systems", 0x29 => "seta", 0x30 => "infogrames", 0x31 => "nintendo", 0x32 => "bandai", 0x33 => Localization.GBC_see_above, 0x34 => "konami", 0x35 => "hector", 0x38 => "Capcom", 0x39 => "Banpresto", 0x3C => "*entertainment i", 0x3E => "gremlin", 0x41 => "Ubisoft", 0x42 => "Atlus", 0x44 => "Malibu", 0x46 => "angel", 0x47 => "spectrum holoby", 0x49 => "irem", 0x4A => "virgin", 0x4D => "malibu", 0x4F => "u.s. gold", 0x50 => "absolute", 0x51 => "acclaim", 0x52 => "activision", 0x53 => "american sammy", 0x54 => "gametek", 0x55 => "park place", 0x56 => "ljn", 0x57 => "matchbox", 0x59 => "milton bradley", 0x5A => "mindscape", 0x5B => "romstar", 0x5C => "naxat soft", 0x5D => "tradewest", 0x60 => "titus", 0x61 => "virgin", 0x67 => "ocean", 0x69 => "electronic arts", 0x6E => "elite systems", 0x6F => "electro brain", 0x70 => "Infogrammes", 0x71 => "Interplay", 0x72 => "broderbund", 0x73 => "sculptered soft", 0x75 => "the sales curve", 0x78 => "t*hq", 0x79 => "accolade", 0x7A => "triffix entertainment", 0x7C => "microprose", 0x7F => "kemco", 0x80 => "misawa entertainment", 0x83 => "lozc", 0x86 => "tokuma shoten intermedia", 0x8B => "bullet-proof software", 0x8C => "vic tokai", 0x8E => "ape", 0x8F => "i'max", 0x91 => "chun soft", 0x92 => "video system", 0x93 => "tsuburava", 0x95 => "varie", 0x96 => "yonezawa/s'pal", 0x97 => "kaneko", 0x99 => "arc", 0x9A => "nihon bussan", 0x9B => "Tecmo", 0x9C => "imagineer", 0x9D => "Banpresto", 0x9F => "nova", 0xA1 => "Hori electric", 0xA2 => "Bandai", 0xA4 => "Konami", 0xA6 => "kawada", 0xA7 => "takara", 0xA9 => "technos japan", 0xAA => "broderbund", 0xAC => "Toei animation", 0xAD => "toho", 0xAF => "Namco", 0xB0 => "Acclaim", 0xB1 => "ascii or nexoft", 0xB2 => "Bandai", 0xB4 => "Enix", 0xB6 => "HAL", 0xB7 => "SNK", 0xB9 => "pony canyon", 0xBA => "*culture brain o", 0xBB => "Sunsoft", 0xBD => "Sony imagesoft", 0xBF => "sammy", 0xC0 => "Taito", 0xC2 => "Kemco", 0xC3 => "Squaresoft", 0xC4 => "tokuma shoten intermedia", 0xC5 => "data east", 0xC6 => "tonkin house", 0xC8 => "koei", 0xC9 => "ufl", 0xCA => "ultra", 0xCB => "vap", 0xCC => "use", 0xCD => "meldac", 0xCE => "*pony canyon or", 0xCF => "angel", 0xD0 => "Taito", 0xD1 => "sofel", 0xD2 => "quest", 0xD3 => "sigma enterprises", 0xD4 => "ask kodansha", 0xD6 => "naxat soft", 0xD7 => "copya systems", 0xD9 => "Banpresto", 0xDA => "tomy", 0xDB => "ljn", 0xDD => "ncs", 0xDE => "human", 0xDF => "altron", 0xE0 => "jaleco", 0xE1 => "towachiki", 0xE2 => "uutaka", 0xE3 => "varie", 0xE5 => "epoch", 0xE7 => "athena", 0xE8 => "asmik", 0xE9 => "natsume", 0xEA => "king records", 0xEB => "atlus", 0xEC => "Epic/Sony records", 0xEE => "igs", 0xF0 => "a wave", 0xF3 => "extreme entertainment", 0xFF => "ljn", _ => Localization.Unknown_licensee }; } string licenseeNew = StringHandlers.CToString(headerLicenseeNew); return licenseeNew switch { "00" => Localization.none_licensee, "01" => "Nintendo R&D1", "08" => "Capcom", "13" => "Electronic Arts", "18" => "Hudson Soft", "19" => "b-ai", "20" => "kss", "22" => "pow", "24" => "PCM Complete", "25" => "san-x", "28" => "Kemco Japan", "29" => "seta", "30" => "Viacom", "31" => "Nintendo", "32" => "Bandai", "33" => "Ocean / Acclaim", "34" => "Konami", "35" => "Hector", "37" => "Taito", "38" => "Hudson", "39" => "Banpresto", "41" => "Ubi Soft", "42" => "Atlus", "44" => "Malibu", "46" => "angel", "47" => "Bullet -Proof", "49" => "irem", "50" => "Absolute", "51" => "Acclaim", "52" => "Activision", "53" => "American sammy", "54" => "Konami", "55" => "Hi tech entertainment", "56" => "LJN", "57" => "Matchbox", "58" => "Mattel", "59" => "Milton Bradley", "60" => "Titus", "61" => "Virgin", "64" => "LucasArts", "67" => "Ocean", "69" => "Electronic Arts", "70" => "Infogrames", "71" => "Interplay", "72" => "Brøderbund", "73" => "sculptured", "75" => "sci", "78" => "THQ", "79" => "Accolade", "80" => "misawa", "83" => "lozc", "86" => "tokuma shoten i", "87" => "tsukuda ori", "91" => "Chunsoft", "92" => "Video system", "93" => "Ocean / Acclaim", "95" => "Varie", "96" => "Yonezawa / s'pal", "97" => "Kaneko", "99" => "Pack in soft", "A4" => "Konami", _ => Localization.Unknown_licensee }; } static uint DecodeRomSize(byte headerRomType) => headerRomType switch { 0 => 32768, 1 => 65536, 2 => 131072, 3 => 262144, 4 => 524288, 5 => 1048576, 6 => 2097152, 7 => 4194304, 8 => 8388608, 0x52 => 1179648, 0x53 => 1310720, 0x54 => 1572864, _ => 0 }; static uint DecodeSaveRamSize(byte headerRamType) => headerRamType switch { 0 => 0, 1 => 2048, 2 => 8192, 3 => 32768, 4 => 131072, 5 => 65536, _ => 0 }; static string DecodeCartridgeType(byte headerRomType) => headerRomType switch { 0x00 => Localization.ROM_only, 0x01 => Localization.ROM_and_MBC1, 0x02 => Localization.ROM_MBC1_and_RAM, 0x03 => Localization.ROM_MBC1_RAM_and_battery, 0x05 => Localization.ROM_and_MBC2, 0x06 => Localization.ROM_MBC2_and_battery, 0x08 => Localization.ROM_and_RAM, 0x09 => Localization.ROM_RAM_and_battery, 0x0B => Localization.ROM_and_MMM01, 0x0C => Localization.ROM_MMM01_and_RAM, 0x0D => Localization.ROM_MMM01_RAM_and_battery, 0x0F => Localization.ROM_MBC3_timer_and_battery, 0x10 => Localization.ROM_MBC3_RAM_timer_and_battery, 0x11 => Localization.ROM_and_MBC3, 0x12 => Localization.ROM_MBC3_and_RAM, 0x13 => Localization.ROM_MBC3_RAM_and_battery, 0x19 => Localization.ROM_and_MBC5, 0x1A => Localization.ROM_MBC5_and_RAM, 0x1B => Localization.ROM_MBC5_RAM_and_battery, 0x1C => Localization.ROM_MBC5_and_vibration_motor, 0x1D => Localization.ROM_MBC5_RAM_and_vibration_motor, 0x1E => Localization .ROM_MBC5_RAM_battery_and_vibration_motor, 0x20 => Localization.ROM_and_MBC6, 0x22 => Localization .ROM_MBC7_RAM_battery_light_sensor_and_vibration_motor, 0xFC => Localization.Pocket_Camera, 0xFD => Localization.ROM_and_TAMA5, 0xFE => Localization.ROM_and_HuC_3, 0xFF => Localization.ROM_and_HuC_1, _ => Localization.Unknown_cartridge_type }; #region Nested type: Header [StructLayout(LayoutKind.Sequential, Pack = 1)] [SwapEndian] partial struct Header { /// Usually 0x00 (NOP) public byte Opcode1; /// Usually 0xC3 (JP) public byte Opcode2; /// Jump destination public ushort Start; /// Boot logo, checked by boot ROM [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] public byte[] Logo; /// Game name, ASCIIZ, if last byte has 7-bit set, that byte is Game Boy Color type [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] Name; /// New licensee code [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] LicenseeNew; /// Super Game Boy Flag public byte Sgb; /// ROM type public byte RomType; /// ROM size public byte RomSize; /// SRAM size public byte SramSize; /// Country code public byte Country; /// Licensee code public byte Licensee; /// Game revision public byte Revision; /// Header checksum public byte HeaderChecksum; /// Cartridge checksum public ushort Checksum; } #endregion }