/* CART header support library */ #include #include #include "cart.h" /* 20071227 bkw: cart_types list was made from atari800-2.0.3/DOC/cart.txt I didn't use any code from atari800, since it's GPL and this code is WTFPL. Format of CART header (from atari800 cart.txt): first 4 bytes containing 'C' 'A' 'R' 'T'. next 4 bytes containing cartridge type in MSB format (see the table below). next 4 bytes containing cartridge checksum in MSB format (ROM only). next 4 bytes are currently unused (zero). followed immediately with the ROM data: 4, 8, 16, 32, 40, 64, 128, 256, 512 or 1024 kilobytes. */ /* 20071227 bkw: this list is complete as of Atari800 2.0.3 */ /* 20220827 bkw: list now complete as of Atari800 5.0.0 */ /* 20240421 bkw: list now complete as of Atari800 5.2.0. The cart type numbers now have a gap in them: type 160 is valid, but types 104 to 159 are not. */ cart_t cart_types[] = { /* Id */ /* Machine, Size, Name */ /* 0 */ { M_INVALID, 0, 0 }, /* 0 is invalid type */ /* 1 */ { M_ATARI8, 8, "Standard 8 KB" }, /* 2 */ { M_ATARI8, 16, "Standard 16 KB" }, /* 3 */ { M_ATARI8, 16, "OSS two chip 16 KB cartridge (034M)" }, /* 4 */ { M_5200, 32, "Standard 32 KB 5200" }, /* 5 */ { M_ATARI8, 32, "DB 32 KB" }, /* 6 */ { M_5200, 16, "Two chip 16 KB 5200" }, /* 7 */ { M_5200, 40, "Bounty Bob Strikes Back 40 KB 5200" }, /* 8 */ { M_ATARI8, 64, "64 KB Williams" }, /* 9 */ { M_ATARI8, 64, "Express 64 KB" }, /* 10 */ { M_ATARI8, 64, "Diamond 64 KB" }, /* 11 */ { M_ATARI8, 64, "SpartaDos X 64 KB" }, /* 12 */ { M_ATARI8, 32, "XEGS 32 KB" }, /* 13 */ { M_ATARI8, 64, "XEGS 64 KB (banks 0-7)" }, /* 14 */ { M_ATARI8, 128, "XEGS 128 KB" }, /* 15 */ { M_ATARI8, 16, "OSS one chip 16 KB" }, /* 16 */ { M_5200, 16, "One chip 16 KB 5200" }, /* 17 */ { M_ATARI8, 128, "Decoded Atrax 128 KB" }, /* 18 */ { M_ATARI8, 40, "Bounty Bob Strikes Back 40 KB" }, /* 19 */ { M_5200, 8, "Standard 8 KB 5200" }, /* 20 */ { M_5200, 4, "Standard 4 KB 5200" }, /* 21 */ { M_ATARI8, 8, "Right slot 8 KB" }, /* 22 */ { M_ATARI8, 32, "32 KB Williams" }, /* 23 */ { M_ATARI8, 256, "XEGS 256 KB" }, /* 24 */ { M_ATARI8, 512, "XEGS 512 KB" }, /* 25 */ { M_ATARI8, 1024, "XEGS 1 MB" }, /* 26 */ { M_ATARI8, 16, "MegaCart 16 KB" }, /* 27 */ { M_ATARI8, 32, "MegaCart 32 KB" }, /* 28 */ { M_ATARI8, 64, "MegaCart 64 KB" }, /* 29 */ { M_ATARI8, 128, "MegaCart 128 KB" }, /* 30 */ { M_ATARI8, 256, "MegaCart 256 KB" }, /* 31 */ { M_ATARI8, 512, "MegaCart 512 KB" }, /* 32 */ { M_ATARI8, 1024, "MegaCart 1 MB" }, /* 33 */ { M_ATARI8, 32, "Switchable XEGS 32 KB" }, /* 34 */ { M_ATARI8, 64, "Switchable XEGS 64 KB" }, /* 35 */ { M_ATARI8, 128, "Switchable XEGS 128 KB" }, /* 36 */ { M_ATARI8, 256, "Switchable XEGS 256 KB" }, /* 37 */ { M_ATARI8, 512, "Switchable XEGS 512 KB" }, /* 38 */ { M_ATARI8, 1024, "Switchable XEGS 1 MB" }, /* 39 */ { M_ATARI8, 8, "Phoenix 8 KB" }, /* 40 */ { M_ATARI8, 16, "Blizzard 16 KB" }, /* 41 */ { M_ATARI8, 128, "Atarimax 128 KB Flash" }, /* 42 */ { M_ATARI8, 1024, "Atarimax 1 MB Flash (old)" }, /* 43 */ { M_ATARI8, 128, "SpartaDos X 128 KB" }, /* 44 */ { M_ATARI8, 8, "OSS 8 KB" }, /* 45 */ { M_ATARI8, 16, "OSS two chip 16 KB (043M)" }, /* 46 */ { M_ATARI8, 4, "Blizzard 4 KB" }, /* 47 */ { M_ATARI8, 32, "AST 32 KB" }, /* 48 */ { M_ATARI8, 64, "Atrax SDX 64 KB" }, /* 49 */ { M_ATARI8, 128, "Atrax SDX 128 KB" }, /* 50 */ { M_ATARI8, 64, "Turbosoft 64 KB" }, /* 51 */ { M_ATARI8, 128, "Turbosoft 128 KB" }, /* 52 */ { M_ATARI8, 32, "Ultracart 32 KB" }, /* 53 */ { M_ATARI8, 8, "Low bank 8 KB" }, /* 54 */ { M_ATARI8, 128, "SIC! 128 KB" }, /* 55 */ { M_ATARI8, 256, "SIC! 256 KB" }, /* 56 */ { M_ATARI8, 512, "SIC! 512 KB" }, /* 57 */ { M_ATARI8, 2, "Standard 2 KB" }, /* 58 */ { M_ATARI8, 4, "Standard 4 KB" }, /* 59 */ { M_ATARI8, 4, "Right slot 4 KB" }, /* 60 */ { M_ATARI8, 32, "Blizzard 32 KB" }, /* 61 */ { M_ATARI8, 2048, "MegaMax 2 MB" }, /* 62 */ { M_ATARI8,131072, "The!Cart 128 MB" }, /* 63 */ { M_ATARI8, 4096, "Flash MegaCart 4 MB" }, /* 64 */ { M_ATARI8, 2048, "MegaCart 2 MB" }, /* 65 */ { M_ATARI8, 32768, "The!Cart 32 MB" }, /* 66 */ { M_ATARI8, 65536, "The!Cart 64 MB" }, /* 67 */ { M_ATARI8, 64, "XEGS 64 KB (banks 8-15)" }, /* 68 */ { M_ATARI8, 128, "Atrax 128 KB" }, /* 69 */ { M_ATARI8, 32, "aDawliah 32 KB" }, /* 70 */ { M_ATARI8, 64, "aDawliah 64 KB" }, /* 71 */ { M_5200, 64, "Super Cart 64 KB 5200" }, /* 72 */ { M_5200, 128, "Super Cart 128 KB 5200" }, /* 73 */ { M_5200, 256, "Super Cart 256 KB 5200" }, /* 74 */ { M_5200, 512, "Super Cart 512 KB 5200" }, /* 75 */ { M_ATARI8, 1024, "Atarimax 1 MB Flash (new)" }, /* 76 */ { M_ATARI8, 16, "16 KB Williams cartridge" }, /* 77 */ { M_ATARI8, 8, "MIO diagnostics 8KB cartridge" }, /* 78 */ { M_ATARI8, 8, "Telelink II cartridge" }, /* 79 */ { M_ATARI8, 16, "Pronto cartridge" }, /* 80 */ { M_ATARI8, 64, "JRC64 cartridge (linear)" }, /* 81 */ { M_ATARI8, 64, "MDDOS cartridge" }, /* 82 */ { M_ATARI8, 32, "COS32 cartridge" }, /* 83 */ { M_ATARI8, 1024, "SIC+ 1024 KB cartridge" }, /* 84 */ { M_ATARI8, 1024, "Corina 1M+8K EEPROM" }, /* 85 */ { M_ATARI8, 512, "Corina 512K + 512K SRAM + 8K EEPROM" }, /* 86 */ { M_ATARI8, 8, "XE Multicart (8KB)" }, /* 87 */ { M_ATARI8, 16, "XE Multicart (16KB)" }, /* 88 */ { M_ATARI8, 32, "XE Multicart (32KB)" }, /* 89 */ { M_ATARI8, 64, "XE Multicart (64KB)" }, /* 90 */ { M_ATARI8, 128, "XE Multicart (128KB)" }, /* 91 */ { M_ATARI8, 256, "XE Multicart (256KB)" }, /* 92 */ { M_ATARI8, 512, "XE Multicart (512KB)" }, /* 93 */ { M_ATARI8, 1024, "XE Multicart (1024KB)" }, /* 94 */ { M_ATARI8, 64, "Ram-Cart 64 KB cartridge" }, /* 95 */ { M_ATARI8, 128, "Ram-Cart 128 KB cartridge" }, /* 96 */ { M_ATARI8, 256, "Double Ram-Cart 2x128/256 KB cartridge" }, /* 97 */ { M_ATARI8, 1024, "Ram-Cart 1 MB cartridge" }, /* 98 */ { M_ATARI8, 2048, "Ram-Cart 2 MB cartridge" }, /* 99 */ { M_ATARI8, 4096, "Ram-Cart 4 MB cartridge" }, /* 100 */ { M_ATARI8, 8192, "Ram-Cart 8 MB cartridge" }, /* 101 */ { M_ATARI8, 16384, "Ram-Cart 16 MB cartridge" }, /* 102 */ { M_ATARI8, 32768, "Ram-Cart 32 MB cartridge" }, /* 103 */ { M_ATARI8, 32, "SiDiCar 32 KB cartridge" }, /* 104 */ { M_INVALID, 0, 0 }, /* 105 */ { M_INVALID, 0, 0 }, /* 106 */ { M_INVALID, 0, 0 }, /* 107 */ { M_INVALID, 0, 0 }, /* 108 */ { M_INVALID, 0, 0 }, /* 109 */ { M_INVALID, 0, 0 }, /* 110 */ { M_INVALID, 0, 0 }, /* 111 */ { M_INVALID, 0, 0 }, /* 112 */ { M_INVALID, 0, 0 }, /* 113 */ { M_INVALID, 0, 0 }, /* 114 */ { M_INVALID, 0, 0 }, /* 115 */ { M_INVALID, 0, 0 }, /* 116 */ { M_INVALID, 0, 0 }, /* 117 */ { M_INVALID, 0, 0 }, /* 118 */ { M_INVALID, 0, 0 }, /* 119 */ { M_INVALID, 0, 0 }, /* 120 */ { M_INVALID, 0, 0 }, /* 121 */ { M_INVALID, 0, 0 }, /* 122 */ { M_INVALID, 0, 0 }, /* 123 */ { M_INVALID, 0, 0 }, /* 124 */ { M_INVALID, 0, 0 }, /* 125 */ { M_INVALID, 0, 0 }, /* 126 */ { M_INVALID, 0, 0 }, /* 127 */ { M_INVALID, 0, 0 }, /* 128 */ { M_INVALID, 0, 0 }, /* 129 */ { M_INVALID, 0, 0 }, /* 130 */ { M_INVALID, 0, 0 }, /* 131 */ { M_INVALID, 0, 0 }, /* 132 */ { M_INVALID, 0, 0 }, /* 133 */ { M_INVALID, 0, 0 }, /* 134 */ { M_INVALID, 0, 0 }, /* 135 */ { M_INVALID, 0, 0 }, /* 136 */ { M_INVALID, 0, 0 }, /* 137 */ { M_INVALID, 0, 0 }, /* 138 */ { M_INVALID, 0, 0 }, /* 139 */ { M_INVALID, 0, 0 }, /* 140 */ { M_INVALID, 0, 0 }, /* 141 */ { M_INVALID, 0, 0 }, /* 142 */ { M_INVALID, 0, 0 }, /* 143 */ { M_INVALID, 0, 0 }, /* 144 */ { M_INVALID, 0, 0 }, /* 145 */ { M_INVALID, 0, 0 }, /* 146 */ { M_INVALID, 0, 0 }, /* 147 */ { M_INVALID, 0, 0 }, /* 148 */ { M_INVALID, 0, 0 }, /* 149 */ { M_INVALID, 0, 0 }, /* 150 */ { M_INVALID, 0, 0 }, /* 151 */ { M_INVALID, 0, 0 }, /* 152 */ { M_INVALID, 0, 0 }, /* 153 */ { M_INVALID, 0, 0 }, /* 154 */ { M_INVALID, 0, 0 }, /* 155 */ { M_INVALID, 0, 0 }, /* 156 */ { M_INVALID, 0, 0 }, /* 157 */ { M_INVALID, 0, 0 }, /* 158 */ { M_INVALID, 0, 0 }, /* 159 */ { M_INVALID, 0, 0 }, /* 160 */ { M_ATARI8, 64, "JRC64 cartridge (interleaved)" }, }; const int MAX_CART_TYPE = (sizeof(cart_types)/sizeof(cart_t))-1; /* helper functions. get_msb_dword() and put_msb_dword() are written the way they are in order to avoid endian-ness issues caused by casts or calls to some endian-aware function like htonl(). */ static unsigned int get_msb_dword(unsigned char *cart, int offset) { return (cart[offset] << 24) | (cart[offset+1] << 16) | (cart[offset+2] << 8) | (cart[offset+3]); } static void put_msb_dword(unsigned char *cart, int offset, unsigned int data) { int i; for(i=3; i>=0; i--) { cart[offset+i] = data & 0xff; data >>= 8; } } /* has_cart_signature() returns boolean */ int has_cart_signature(unsigned char *cart) { return memcmp(cart, CART_SIGNATURE, 4) == 0; } void cart_dump_header(unsigned char *cart, int calc_cksum) { char *type = "UNKNOWN"; unsigned int id = get_cart_type(cart); unsigned int real_id = get_msb_dword(cart, CART_TYPE_OFFSET); if(id) type = cart_types[id].name; else calc_cksum = 0; /* Show real type, if get_cart_type() returned zero */ fprintf(stderr, "CART signature: %s\n", (has_cart_signature(cart) ? "Present" : "MISSING")); fprintf(stderr, "Cartridge type: %s (ID %d)\n", type, real_id); fprintf(stderr, "ROM size: "); if(id) fprintf(stderr, "%d bytes + 16 byte header\n", get_cart_size(cart)); else fprintf(stderr, "UNKNOWN\n"); fprintf(stderr, "Machine type: "); switch(get_cart_machine(cart)) { case M_ATARI8: fprintf(stderr, "Atari 8-bit computer\n"); break; case M_5200: fprintf(stderr, "Atari 5200\n"); break; default: fprintf(stderr, "UNKNOWN\n"); break; } fprintf(stderr, "CART checksum: 0x%08x", get_cart_checksum(cart)); if(calc_cksum) { fputs((cart_checksum_ok(cart) ? "OK" : "BAD"), stderr); } fputc('\n', stderr); } /* get_cart_type() returns the cartridge ID from the CART header, or zero if it's an invalid/unknown type. */ unsigned int get_cart_type(unsigned char *cart) { unsigned int type = get_msb_dword(cart, CART_TYPE_OFFSET); if(type == 0 || type > MAX_CART_TYPE) return 0; return type; } /* get_cart_size() returns the cartridge ROM size in bytes, NOT including the 16-byte header, or zero if invalid type */ unsigned int get_cart_size(unsigned char *cart) { unsigned int type = get_cart_type(cart); if(type == 0) return 0; return cart_types[type].size * 1024; } /* get_cart_checksum() returns the checksum stored in the header. It does NOT calculate the checksum (use calc_rom_checksum() for that) */ unsigned int get_cart_checksum(unsigned char *cart) { return get_msb_dword(cart, CART_CHECKSUM_OFFSET); } machine_t get_cart_machine(unsigned char *cart) { unsigned int type = get_cart_type(cart); if(type == 0) return M_INVALID; return cart_types[type].machine; } /* calc_rom_checksum() is the odd man out: it takes a pointer to the raw ROM data, NOT to the header+data like all the other functions do! */ unsigned int calc_rom_checksum(unsigned char *rom, int bytes) { unsigned int cksum = 0; while(bytes-- > 0) cksum += *rom++; return cksum; } /* cart_checksum_ok() must be called with a complete .CAR image, not just a header! */ unsigned int cart_checksum_ok(unsigned char *cart) { unsigned int bytes = get_cart_size(cart); unsigned int got = calc_rom_checksum(cart + 16, bytes); unsigned int expected = get_cart_checksum(cart); return (got == expected); } /* create_cart_header() must be supplied with a buffer, which must be at least 16 bytes in size. Returns 0 with buffer unchanged if type was invalid. */ int create_cart_header( unsigned char *buffer, unsigned char *rom, unsigned int type) { if(type == 0 || type > MAX_CART_TYPE) return 0; memcpy(buffer, CART_SIGNATURE, 4); set_cart_type(buffer, type); set_cart_checksum(buffer, calc_rom_checksum(rom, cart_types[type].size * 1024)); set_cart_unused(buffer, 0); return 1; } void set_cart_checksum(unsigned char *cart, unsigned int sum) { put_msb_dword(cart, CART_CHECKSUM_OFFSET, sum); } void set_cart_type(unsigned char *cart, unsigned int type) { put_msb_dword(cart, CART_TYPE_OFFSET, type); } void set_cart_unused(unsigned char *cart, unsigned int data) { put_msb_dword(cart, CART_UNUSED_OFFSET, data); }