static void addCartHardwareInfo(ROMInfo info, CartAdditionalHardware hardware, bool isOriginalFromGBX) { string prefix = isOriginalFromGBX ? "Original cart header claims to have " : "Has "; //I should word that better, but whatevs info.addInfo(prefix + "RAM", hardware.HasFlag(CartAdditionalHardware.RAM), isOriginalFromGBX); info.addInfo(prefix + "battery", hardware.HasFlag(CartAdditionalHardware.Battery), isOriginalFromGBX); info.addInfo(prefix + "RTC", hardware.HasFlag(CartAdditionalHardware.RTC), isOriginalFromGBX); info.addInfo(prefix + "rumble", hardware.HasFlag(CartAdditionalHardware.Rumble), isOriginalFromGBX); info.addInfo(prefix + "accelerometer", hardware.HasFlag(CartAdditionalHardware.Accelerometer), isOriginalFromGBX); }
public CartInfo(string mapper, CartAdditionalHardware flags = CartAdditionalHardware.None) { this.mapper = mapper; this.flags = flags; }
public override void addROMInfo(ROMInfo info, ROMFile file) { WrappedInputStream f = file.stream; f.Seek(-4, SeekOrigin.End); string magic = f.read(4, Encoding.ASCII); bool isGBX = false; if ("GBX!".Equals(magic)) { //See also: http://hhug.me/gbx/1.0 isGBX = true; f.Seek(-16, SeekOrigin.End); int footerSize = f.readIntBE(); int majorVersion = f.readIntBE(); int minorVersion = f.readIntBE(); info.addInfo("GBX footer size", footerSize, ROMInfo.FormatMode.SIZE); info.addInfo("GBX version", String.Format("{0}.{1}", majorVersion, minorVersion)); if (majorVersion == 1) { f.Seek(-footerSize, SeekOrigin.End); string mapperID = f.read(4, Encoding.ASCII).TrimEnd('\0'); CartAdditionalHardware hardware = CartAdditionalHardware.None; if (f.read() == 1) { hardware |= CartAdditionalHardware.Battery; } if (f.read() == 1) { hardware |= CartAdditionalHardware.Rumble; } if (f.read() == 1) { hardware |= CartAdditionalHardware.RTC; } info.addInfo("Mapper", mapperID, GBX_MAPPERS); addCartHardwareInfo(info, hardware, false); int unused = f.read(); info.addInfo("GBX unused", unused, true); int gbxRomSize = f.readIntBE(); int gbxRamSize = f.readIntBE(); info.addInfo("ROM size", gbxRomSize, ROMInfo.FormatMode.SIZE); info.addInfo("Save size", gbxRamSize, ROMInfo.FormatMode.SIZE); byte[] gbxFlags = f.read(32); info.addInfo("GBX flags", gbxFlags, true); } } f.Position = 0x100; byte[] startVector = f.read(4); info.addInfo("Entry point", startVector, true); byte[] nintendoLogo = f.read(48); info.addInfo("Nintendo logo", nintendoLogo, true); info.addInfo("Nintendo logo valid?", isNintendoLogoEqual(nintendoLogo)); bool isCGB; //Hoo boy this is gonna be tricky hold my... I don't have a beer right now byte[] title = f.read(16); //This gets tricky because only early games use the full 16 characters and then //at some point the last byte became the CGB flag, and then afterwards 4 characters //became the product code leaving only 11 characters for the title and there's not //really a 100% accurate heuristic to detect if the game uses 11 or 15 characters //Most emulators and whatnot use 11 if the game uses a new licensee code and 15 //otherwise, but stuff like Pokemon Yellow and Gameboy Camera disprove that theory int titleLength = 16; //At least we can reliably detect if the game uses a CGB flag or not because the only //two valid values aren't valid inside titles if (CGB_FLAGS.ContainsKey(title[15])) { isCGB = title[15] != 0; titleLength = 15; info.addInfo("Is colour", title[15], CGB_FLAGS); info.addInfo("Platform", title[15] == 0xc0 ? "Game Boy Color" : "Game Boy"); //Here's the tricky part... well, we know that any game old enough to not //have a CGB flag isn't going to have a product code either, because those are new //and also I looked at every single commercially released GB/GBC ROM I have to figure out //what works and what doesn't //We also know that any game that uses the _old_ licensee code _isn't_ going to have a //product code, but a game that uses the new licensee code might have a product code and //also might not as previously mentioned //We can also see that any game that is exclusive to the Game Boy Color will have a //product code - but not necessarily a game that is merely GBC enhanced but GB compatible //With that in mind... for now, I'll only use 11 characters + product code if I know for sure it has one //The other way would be to check if there are extra characters beyond the first null character, because //you're not allowed to have a null character in the middle of a title so if I see characters after that, then //it's the manufacturer code (which is always in the same place) //So if the null character appears inside the 11 bytes, then it definitely ends the string, and then we can //just check to see if there's a manufacturer code afterwards int lastNullCharIndex = Array.IndexOf(title, 0); if (title[15] == 0xc0 || ((lastNullCharIndex != -1 && lastNullCharIndex <= 11) && title[14] != 0)) { titleLength = 11; string productCode = Encoding.ASCII.GetString(title, 11, 4); info.addInfo("Product code", productCode); //No documentation I've found at all knows what the product type means! It looks like it works the same way //as GBA, right down to V being the product type for rumble-enabled games and Kirby's Tilt and Tumble //using K. How about that? //Anyway yeah it all works out except for homebrews that stuff up my heuristic and don't really have //product codes, and Robopon games which have a H for game type? That probably means something involving their infrared stuff char gameType = productCode[0]; info.addInfo("Type", gameType, GBA.GBA_GAME_TYPES); string shortTitle = productCode.Substring(1, 2); info.addInfo("Short title", shortTitle); char country = productCode[3]; info.addInfo("Country", country, NintendoCommon.COUNTRIES); } } else { info.addInfo("Platform", "Game Boy"); isCGB = false; info.addInfo("Is colour", false); } //Now we can add what's left of the title info.addInfo("Internal name", Encoding.ASCII.GetString(title, 0, titleLength).TrimEnd('\0', ' ')); string licenseeCode = f.read(2, Encoding.ASCII); bool isSGB = f.read() == 3; info.addInfo("Super Game Boy Enhanced?", isSGB); int cartType = f.read(); int romSize = f.read(); int ramSize = f.read(); addCartTypeInfo(info, cartType, isGBX); if (isGBX) { info.addInfo("Original ROM size", romSize, ROM_SIZES, ROMInfo.FormatMode.SIZE, true); info.addInfo("Original save size", ramSize, RAM_SIZES, ROMInfo.FormatMode.SIZE, true); } else { info.addInfo("ROM size", romSize, ROM_SIZES, ROMInfo.FormatMode.SIZE); info.addInfo("Save size", ramSize, RAM_SIZES, ROMInfo.FormatMode.SIZE); } int destinationCode = f.read(); info.addInfo("Destination code", destinationCode); //Don't want to call this "Region", it's soorrrta what it is but only sorta. 0 is Japan and anything else is non-Japan basically int oldLicensee = f.read(); if (oldLicensee == 0x33) { info.addInfo("Publisher", licenseeCode, NintendoCommon.LICENSEE_CODES); info.addInfo("Uses new licensee", true); } else { info.addInfo("Publisher", String.Format("{0:X2}", oldLicensee), NintendoCommon.LICENSEE_CODES); info.addInfo("Uses new licensee", false); } int version = f.read(); info.addInfo("Version", version); int checksum = f.read(); info.addInfo("Checksum", checksum, ROMInfo.FormatMode.HEX, true); int calculatedChecksum = calcChecksum(f); info.addInfo("Calculated checksum", calculatedChecksum, ROMInfo.FormatMode.HEX, true); info.addInfo("Checksum valid?", checksum == calculatedChecksum); short globalChecksum = f.readShortBE(); info.addInfo("Global checksum", globalChecksum, ROMInfo.FormatMode.HEX, true); short calculatedGlobalChecksum = calcGlobalChecksum(f); info.addInfo("Calculated global checksum", calculatedGlobalChecksum, ROMInfo.FormatMode.HEX, true); info.addInfo("Global checksum valid?", globalChecksum == calculatedGlobalChecksum); if (!isCGB) { //Game Boy Color automatically colorizes some old Game Boy games via hashing the title and other data, albeit it only does this if licensee code = 01 //If I had a way for the user to point to the location of the GBC BIOS, I could do more here, but until then nah f.Position = 0x134; byte[] hashData = f.read(15); byte hash = (byte)hashData.Aggregate(0, (x, y) => (x + y) & 0xff); info.addInfo("GBC palette hash", hash, ROMInfo.FormatMode.HEX); info.addInfo("GBC palette disambiguation value", title[3], ROMInfo.FormatMode.HEX); } }