usrnatc.net


Useful Websites I Like

> Raytracing in one Weekend

Great tutorial on writing a simple ray-tracing engine in C++. Extremely iterative workflow that is very easy to follow.

> Data Structures in Practice

Explanations and real-world examples of some data-structures.

> Website of Øyvind Kolås

This website is a Repository/portfolio/gallery/documentation of digital creations and other artifacts by Øyvind Kolås, tools for experimental media creation, modification, curation and transformation.

> wiby.me

Search engine for websites with no javascript.

> Australian Bank Leaderboard

List of Australian Banks with corresponding interest rates and special requirements. Updated regularly.

> Portable Freeware

Catalogue of portable freeware applications. Mostly for Windows machines (XP - 11). Some applications listed are not as portable as others.

Lex Faster with SIMD

Is your lexer running slow? Probably not. But that doesn't mean you shouldn't speed it up as much as possible. The way some people write software you're bound to have to accommodate parsing files 1GB+. Call it an edge case but its a case nonetheless.

Essentially, all a lexer is required to do is take some raw source code and produce tokens by searching for symbols and keywords that our language defines. Not really that tough of a job so its important to not let the lexer eat up too much of our compiler's runtime.

The implementation of this is based on the following paper:

> Beyond Traditional Lexing - A. Bolfă

The paper views lexing as a series of sub-lexers for identifying; whitespaces, punctuators (single-, double-, and triple-character), identifiers, keywords, numbers, strings, characters, and comments.

Nothing new so far, this is a pretty standard pipeline of a lexer. Usually, especially in educational contexts, this process is handled one character at a time. This paper suggests that this can be optimised by utilising AVX2 instructions to tokenise the source code 32 characters at a time.

Using SIMD instructions for text processing is also not a new idea, however there are still some obstacles to overcome in the process. The paper highlights that text, specifically source code, is full of horizontal dependencies; identifiers or language keywords.

Parsing GameBoy Colour Cartridge (not really)

I recently found my old GameBoy Colour. I loved this thing and still do. Fits in my pocket, when the batteries run out you lose all of your progress because why would I remember to save anything, and you can only view the screen with a light bright enough for use in federal torture methods. Lets parse and view some aspects of a GameBoy cartridge header. For any actual/in-depth information, this link is incredible.

> gbdev.io - The Cartridge Header

Just a quick first note, I'll be writing this in C++ but really leaning heavily into the C aspect for my sanity. This implementation will be quick and dirty, as this project progresses things can be cleaned up but until then I really don't care as long as it works.

First up, I define the locations of some important data in the header like so:

                    
#define CART_HEADER_START_ADDRESS                0x100
#define CART_ENTRY_START_ADDRESS                 0x100
#define CART_NINTENDO_LOGO_START_ADDRESS         0x104
#define CART_TITLE_START_ADDRESS                 0x134
#define CART_MANUFACTURER_CODE_START_ADDRESS     0x13F   // NOTE(nathan): In older cartridges, these
#define CART_CGB_FLAG_ADDRESS                    0x143   // NOTE        : values are included in the Title
#define CART_NEW_LICENSEE_CODE_HIGH_ADDRESS      0x144
#define CART_NEW_LICENSEE_CODE_LOW_ADDRESS       0x145
#define CART_SGB_FLAG_ADDRESS                    0x146
#define CART_CARTRIDGE_TYPE_FLAG_ADDRESS         0x147
#define CART_ROM_SIZE_ADDRESS                    0x148
#define CART_RAM_SIZE_ADDRESS                    0x149
#define CART_DESTINATION_CODE_ADDRESS            0x14A
#define CART_OLD_LICENSEE_CODE_ADDRESS           0x14B
#define CART_ROM_VERSION_MASK_ADDRESS            0x14C
#define CART_HEADER_CHECKSUM_ADDRESS             0x14D  // NOTE(nathan): 8-bit checksum computed from 0x134 - 0x14C
#define CART_GLOBAL_CHECKSUM_START_ADDRESS       0x14E  // NOTE(nathan): 16-bit BE checksum of the sum of all ROM bytes (excl. itself)
                    
                

Great. Now that we know where things live we need to be able to convert them into something meaningful to us humans. We will do this by storing some of the possible values the header values could be referencing.

I'll start off with the easiest ones because why not knock them out of the way. I store the CGB mode flag, SGB mode flag, and the official Nintendo logo directly:

                    
#define CART_CGB_FLAG_CGB_MODE      0xC0
#define CART_CGB_FLAG_NON_CGB_MODE  0x80
#define CART_SGB_FLAG_SGB_MODE      0x03
#define CART_SGB_FLAG_NON_SGB_MODE  0x00

const u8 CART_OFFICIAL_NINTENDO_LOGO[48] = {
    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
};
                    
                

The destination code flag, ROM size, and RAM size are a little bigger but still on the easy side:

                    
const char* CART_DESTINATION_CODES[2] = {
    "JAPAN",
    "OVERSEAS"
};

#define CART_ROM_SIZE_KIB(NumBanks) (32 * (1UL << (NumBanks)))
const char* CART_ROM_SIZES[9] = {
    "32 KiB (2 BANKS)",
    "64 KiB (4 BANKS)",
    "128 KiB (8 BANKS)",
    "256 KiB (16 BANKS)",
    "512 KiB (32 BANKS)",
    "1 MiB (64 BANKS)",
    "2 MiB (128 BANKS)",
    "4 MiB (256 BANKS)",
    "8 MiB (512 BANKS)"
};

const char* CART_RAM_SIZES[6] = {
    "NO RAM",
    "UNUSED",
    "8 KiB (1 * 8KiB)",
    "32 KiB (4 * 8KiB)",
    "128 KiB (16 * 8KiB)",
    "64 KiB (8 * 8KiB)"
};
                    
                

These values were actually really nice. They all started at 0 and they were sequential, which sadly is not the case for some of the values to come. I've defined a little macro to easily convert the ROM size value stored in the header to a value in KiB, super easy.

Next up are a little more annoying, just because there's more typing to do. I store the possible values for the cartridge type and the old licencee names in sparse arrays. The new licensee names are even more annoying becuase they aren't defined sequentially, instead they are stored as 2 ASCII characters that correpsond to a publisher's name. For this I implemented a simple function that uses a switch-case to return the appropriate string value:

                    
const char* CART_CARTRIDGE_TYPES[256] = {
    "ROM ONLY",
    "MBC1",
    "MBC1+RAM",
    "MBC1+RAM+BATTERY",
    NULL,
    "MBC2",
    "MBC2+BATTERY",
    NULL,
    "ROM+RAM",          // NOTE(nathan): Unused in licensed cartridges
    "ROM+RAM+BATTERY",  // NOTE(nathan): Unused in licensed cartridges
    NULL,
    "MMM01",
    "MMM01+RAM",
    "MMM01+RAM+BATTERY",
    NULL,
    "MBC3+TIMER+BATTERY",
    "MBC3+TIMER+RAM+BATTERY",
    "MBC3",
    "MBC3+RAM",
    "MBC3+RAM+BATTERY",
    NULL, NULL, NULL, NULL, NULL,
    "MBC5",
    "MBC5+RAM",
    "MBC5+RAM+BATTERY",
    "MBC5+RUMBLE",
    "MBC5+RUMBLE+RAM",
    "MBC5+RUMBLE+RAM+BATTERY",
    NULL,
    "MBC6",
    NULL,
    "MBC7+SENSOR+RUMBLE+RAM+BATTERY",
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL,
    "POCKET CAMERA",
    "BANDAI TAMA5",
    "HuC3",
    "HuC1+RAM+BATTERY"
};

const char* CART_OLD_LICENSEES[256] = {
    "None",
    "Nintendo",
    NULL, NULL, NULL, NULL, NULL, NULL,
    "Capcom",
    "HOT-B",
    "Jaleco",
    "Coconuts Japan",
    "Elite Systems",
    NULL, NULL, NULL, NULL, NULL, NULL,
    "EA (Electronic Arts)",
    NULL, NULL, NULL, NULL,
    "Hudson Soft",
    "ITC Entertainment",
    "Yanoman",
    NULL, NULL,
    "Japan Clary",
    NULL,
    "Virgin Games Ltd.3",
    NULL, NULL, NULL, NULL,
    "PCM Complete",
    "San-X",
    NULL, NULL,
    "Kemco",
    "SETA Corporation",
    NULL, NULL, NULL, NULL, NULL, NULL,
    "Infogrames5",
    "Nintendo",
    "Bandai",
    "(SEE OLD LICENSEE CODE)",
    "Konami",
    "HectorSoft",
    NULL, NULL,
    "Capcom",
    "Banpresto",
    NULL, NULL,
    ".Entertainment i",
    NULL,
    "Gremlin",
    NULL, NULL,
    "Ubi Soft1",
    "tlus",
    NULL,
    "Malibu Interactive",
    NULL,
    "Angel",
    "Spectrum Holoby",
    NULL,
    "Irem",
    "Virgin Games Ltd.3",
    NULL, NULL,
    "Malibu Interactive",
    NULL,
    "U.S. Gold",
    "Absolute",
    "Acclaim Entertainment",
    "Activision",
    "Sammy USA Corporation",
    "GameTek",
    "Park Place",
    "LJN",
    "Matchbox",
    NULL,
    "Milton Bradley Company",
    "Mindscape",
    "Romstar",
    "Naxat Soft13",
    "Tradewest",
    NULL, NULL,
    "Titus Interactive",
    "Virgin Games Ltd.3",
    NULL, NULL, NULL, NULL, NULL,
    "Ocean Software",
    NULL,
    "EA (Electronic Arts)",
    NULL, NULL, NULL, NULL,
    "Elite Systems",
    "Electro Brain",
    "Infogrames5",
    "Interplay Entertainment",
    "Broderbund",
    "Sculptured Software6",
    NULL,
    "The Sales Curve Limited7",
    NULL, NULL,
    "THQ",
    "Accolade",
    "Triffix Entertainment",
    NULL,
    "Microprose",
    NULL, NULL,
    "Kemco",
    "Misawa Entertainment",
    NULL, NULL,
    "Lozc",
    NULL, NULL,
    "Tokuma Shoten",
    NULL, NULL, NULL, NULL,
    "Bullet-Proof Software2",
    "Vic Tokai",
    NULL,
    "Ape",
    "I'Max",
    NULL,
    "Chunsoft Co.8",
    "Video System",
    "Tsubaraya Productions",
    NULL,
    "Varie",
    "Yonezawa/S'Pal",
    "Kemco",
    NULL,
    "Arc",
    "Nihon Bussan",
    "Tecmo",
    "Imagineer",
    "Banpresto",
    NULL,
    "Nova",
    NULL,
    "Hori Electric",
    "Bandai",
    NULL,
    "Konami",
    NULL,
    "Kawada",
    "Takara",
    NULL,
    "Technos Japan",
    "Broderbund",
    NULL,
    "Toei Animation",
    "Toho",
    NULL,
    "Namco",
    "Acclaim Entertainment",
    "ASCII Corporation or Nexsoft",
    "Bandai",
    NULL,
    "Square Enix",
    NULL,
    "HAL Laboratory",
    "SNK",
    NULL,
    "Pony Canyon",
    "Culture Brain",
    "Sunsoft",
    NULL,
    "Sony Imagesoft",
    NULL,
    "Sammy Corporation",
    "Taito",
    NULL,
    "Kemco",
    "Square",
    "Tokuma Shoten",
    "Data East",
    "Tonkinhouse",
    NULL,
    "Koei",
    "UFL",
    "Ultra",
    "Vap",
    "Use Corporation",
    "Meldac",
    "Pony Canyon",
    "Angel",
    "Taito",
    "Sofel",
    "Quest",
    "Sigma Enterprises",
    "ASK Kodansha Co.",
    NULL,
    "Naxat Soft13",
    "Copya System",
    NULL,
    "Banpresto",
    "Tomy",
    "LJN",
    NULL,
    "NCS",
    "Human",
    "Altron",
    "Jaleco",
    "Towa Chiki",
    "Yutaka",
    "Varie",
    NULL,
    "Epcoh",
    NULL,
    "Athena",
    "Asmik Ace Entertainment",
    "Natsume",
    "King Records",
    "Atlus",
    "Epic/Sony Records",
    NULL,
    "IGS",
    NULL,
    "A Wave",
    NULL, NULL,
    "Extreme Entertainment",
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL,
    "LJN"
};

internal const char*
CartNewLicenseeName(u16 NewLicenseeCode)
{
    switch (NewLicenseeCode) {
        case 0x3030: return "None";
        case 0x3031: return "Nintendo Research & Development 1";
        case 0x3038: return "Capcom";
        case 0x3133: return "EA (Electronic Arts)";
        case 0x3138: return "Hudson Soft";
        case 0x3139: return "B-AI";
        case 0x3230: return "KSS";
        case 0x3232: return "Planning Office WADA";
        case 0x3234: return "PCM Complete";
        case 0x3235: return "San-X";
        case 0x3238: return "Kemco";
        case 0x3239: return "SETA Corporation";
        case 0x3330: return "Viacom";
        case 0x3331: return "Nintendo";
        case 0x3332: return "Bandai";
        case 0x3333: return "Ocean Software/Acclaim Entertainment";
        case 0x3334: return "Konami";
        case 0x3335: return "HectorSoft";
        case 0x3337: return "Taito";
        case 0x3338: return "Hudson Soft";
        case 0x3339: return "Banpresto";
        case 0x3431: return "Ubi Soft";
        case 0x3432: return "Atlus";
        case 0x3434: return "Malibu Interactive";
        case 0x3436: return "Angel";
        case 0x3437: return "Bullet-Proof Software";
        case 0x3439: return "Irem";
        case 0x3530: return "Absolute";
        case 0x3531: return "Acclaim Entertainment";
        case 0x3532: return "Activision";
        case 0x3533: return "Sammy USA Corporation";
        case 0x3534: return "Konami";
        case 0x3535: return "Hi Tech Expressions";
        case 0x3536: return "LJN";
        case 0x3537: return "Matchbox";
        case 0x3538: return "Mattel";
        case 0x3539: return "Milton Bradley Company";
        case 0x3630: return "Titus Interactive";
        case 0x3631: return "Virgin Games Ltd.";
        case 0x3634: return "Lucasfilm Games";
        case 0x3637: return "Ocean Software";
        case 0x3639: return "EA (Electronic Arts)";
        case 0x3730: return "Infogrames";
        case 0x3731: return "Interplay Entertainment";
        case 0x3732: return "Broderbund";
        case 0x3733: return "Sculptured Software";
        case 0x3735: return "The Sales Curve Limited";
        case 0x3738: return "THQ";
        case 0x3739: return "Accolade";
        case 0x3830: return "Misawa Entertainment";
        case 0x3833: return "lozc";
        case 0x3836: return "Tokuma Shoten";
        case 0x3837: return "Tsukuda Original";
        case 0x3931: return "Chunsoft Co.";
        case 0x3932: return "Video System";
        case 0x3933: return "Ocean Software/Acclaim Entertainment";
        case 0x3935: return "Varie";
        case 0x3936: return "Yonezawa/s'pal";
        case 0x3937: return "Kaneko";
        case 0x3939: return "Pack-In-Video";
        case 0x3948: return "Bottom Up";
        case 0x4134: return "Konami (Yu-Gi-Oh!)";
        case 0x424C: return "MTO";
        case 0x444B: return "Kodansha";
        default: return "Unknown New Licensee";
    }
}
                    
                

Once again nothing really ground-breaking or difficult, just some simple look-up tables to make things fast and easy. Just out of interest I'll also steal the implementation to validate the header checksum from the GameBoy boot ROM:

                    
internal u8
ValidateROMHeaderChecksum(u8* ROM)
{
    u8 Result = 0;

    for (u16 Address = 0x134; Address <= 0x14C; ++Address)
        Result = Result - ROM[Address] - 1;

    return Result;
}
                    
                

Finally, I think that should just about do it for storing the information I care about. Now I define a stuct to store it all in the same place and pass it around to other functions if I so choose later on.

                    
struct CartridgeHeader {
    u8* NintendoLogo;
    char* Title;
    char* ManufacturerCode;
    u8 ColourFlag;
    u16 NewLicenseeCode;
    u8 SuperFlag;
    u8 Type;
    u8 ROMSize;
    u8 RAMSize;
    u8 DestinationCode;
    u8 OldLicenseeCode;
    u8 ROMVersionNumber;
    u8 Checksum;
    u16 GlobalChecksum;
};
                    
                

I never had many games for the GBC. Me and my 2 brothers each had our own GameBoy, but somehow managed to survive sharing a single actual game cartridge and another 8-in-1 cartridge that I have never been able to find (even just as a ROM file) to this day. That amazing game was Harry Potter and the Chamber of Secrets. What a game. I loved it, I was terrible at it, but I loved it. Looking back its unbelievable how much content they managed to squeeze on to such a small cartridge. The soundtrack was amazing, chiptune Harry Potter never sounded so good. Anyway, that's the ROM I'll be testing my program with. My program will only read the ROM, store the header and then dump the info. You'll notice another struct in the code which I haven't mentioned or shown `Cartridge`. It doesnt do anything other than open the file, read its contents and store it. The file handle/pointer (depending on your OS) is closed immediately after reading the file in, but the memory storing the contents is never freed and that's on purpose. For a program as simple as this memory leaks are a nothing-burger and as the great Raymond Chen has said, "The house is on fire and the alarms are going off, why are you cleaning up?".

Anyway, the code flow goes pretty simply: read the ROM file and store, extract header data and store, print header data, exit. Easy to follow and easy to fix if troubles arise. I say that because very little error handling is being done, this is a toy project after all.

                    
int
main(void)
{
    Cartridge Cart = {};
    Cart = LoadCartridge("Roms/Harry Potter and the Chamber of Secrets (USA, Europe) (En,Fr,De,Es,It,Pt,Nl,Sv,Da).gbc");

    if (Cart.RomSize == 0)
        return 1;

    CartridgeHeader Header = {};

    Header.Title = (char*) Cart.Rom + CART_TITLE_START_ADDRESS;
    Header.ManufacturerCode = (char*) Cart.Rom + CART_MANUFACTURER_CODE_START_ADDRESS;
    Header.ColourFlag = Cart.Rom[CART_CGB_FLAG_ADDRESS];
    Header.SuperFlag = Cart.Rom[CART_SGB_FLAG_ADDRESS];
    Header.Type = Cart.Rom[CART_CARTRIDGE_TYPE_FLAG_ADDRESS];
    Header.NewLicenseeCode = (Cart.Rom[CART_NEW_LICENSEE_CODE_HIGH_ADDRESS] << 8) | (Cart.Rom[CART_NEW_LICENSEE_CODE_LOW_ADDRESS]);
    Header.Type = Cart.Rom[CART_CARTRIDGE_TYPE_FLAG_ADDRESS];
    Header.ROMSize = Cart.Rom[CART_ROM_SIZE_ADDRESS];
    Header.RAMSize = Cart.Rom[CART_RAM_SIZE_ADDRESS];
    Header.DestinationCode = Cart.Rom[CART_DESTINATION_CODE_ADDRESS];
    Header.OldLicenseeCode = Cart.Rom[CART_OLD_LICENSEE_CODE_ADDRESS];
    Header.ROMVersionNumber = Cart.Rom[CART_ROM_VERSION_MASK_ADDRESS];
    Header.Checksum = Cart.Rom[CART_HEADER_CHECKSUM_ADDRESS];
    Header.GlobalChecksum = *(u16*) Cart.Rom + CART_GLOBAL_CHECKSUM_START_ADDRESS;

    printf("*************************************************\n");
    printf("** ROM HEADER INFO                              *\n");
    printf("*************************************************\n");
    printf("** Title                : %s\n", Header.Title);
    printf("** Manufacturer Code    : %s\n", Header.ManufacturerCode);
    printf("** CGB Mode Flag        : 0x%X\n", Header.ColourFlag);
    printf("** New Licensee Code    : %s\n", CartNewLicenseeName(Header.NewLicenseeCode));
    printf("** SGB Mode Flag        : 0x%X\n", Header.SuperFlag);
    printf("** Cartridge Type       : %s\n", CART_CARTRIDGE_TYPES[Header.Type]);
    printf("** ROM Size             : %s\n", CART_ROM_SIZES[Header.ROMSize]);
    printf("** ROM Size Raw         : %lu KiB\n", CART_ROM_SIZE_KIB(Header.ROMSize));
    printf("** RAM Size             : %s\n", CART_RAM_SIZES[Header.RAMSize]);
    printf("** Destination          : %s\n", CART_DESTINATION_CODES[Header.DestinationCode]);
    printf("** Old Licensee Code    : %s\n", CART_OLD_LICENSEES[Header.OldLicenseeCode]);
    printf("** Version              : 0x%X\n", Header.ROMVersionNumber);
    printf("** Checksum             : 0x%X\n", Header.Checksum);
    printf("** Global Checksum      : 0x%X\n", Header.GlobalChecksum);
    printf("*************************************************\n");

    u8 HeaderComputedChecksum = ValidateROMHeaderChecksum(Cart.Rom);

    if (HeaderComputedChecksum == Header.Checksum)
        printf("** Header Checksum      : SUCCESS               *\n");
    else
        printf("** Header Checksum      : FAILURE (0x%X != 0x%X)\n", Header.Checksum, HeaderComputedChecksum);

    printf("*************************************************\n");
    
    return 0;
}
                    
                

And from all of this, we get a nice little output frame of our cartridge ROM header:

		    
*************************************************
** ROM HEADER INFO                              *
*************************************************
** Title                : HPCOSECRETSBH6E�69
** Manufacturer Code    : BH6E�69
** CGB Mode Flag        : 0xC0
** New Licensee Code    : EA (Electronic Arts)
** SGB Mode Flag        : 0x0
** Cartridge Type       : MBC5+RAM+BATTERY
** ROM Size             : 4 MiB (256 BANKS)
** ROM Size Raw         : 4096 KiB
** RAM Size             : 8 KiB (1 * 8KiB)
** Destination          : OVERSEAS
** Old Licensee Code    : (SEE OLD LICENSEE CODE)
** Version              : 0x0
** Checksum             : 0x18
** Global Checksum      : 0xCF2F
*************************************************
** Header Checksum      : SUCCESS               *
*************************************************
		    
		

You'll notice the Title and Manufacturer codes seem to be reading more data than they should be, but everything else looks pretty much exactly how I want it to. I'll leave those problems as a breadcrumb for a future investigation. In fact now that I'm looking at it, its most likely because I'm just callously casting the ROM data to a char*. I'll need to factor in that the Title should only be 11 characters long and I shouldn't assume they will always be NULL terminated. An easy fix for later.

There you have it. A nice simple program that dumps some information from a GameBoy Colour ROM header. Not really useful (yet), but I did spend some time finding some random ROMs and seeing their info. There are also a bunch of things that should/could change about the program such as how I store the Cartridge and CartridgeHeader separately which annoys me but I only have myself to blame and these will prove to have simple solutions. In the future I'll learn more about what these values exactly mean to a GameBoy console, and how I can go about using them to run some games.

P.S. This code has been tested and known to be working with both clang and cl