aboutsummaryrefslogtreecommitdiff
path: root/cart.c
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2022-08-29 16:11:13 -0400
committerB. Watson <urchlay@slackware.uk>2022-08-29 16:11:13 -0400
commite2ba8458a5cfdfacfaf103e7ba97d610afa6c970 (patch)
treecd665e602e6e2b636578a7d3d7894380605dafcc /cart.c
downloadbw-atari8-tools-e2ba8458a5cfdfacfaf103e7ba97d610afa6c970.tar.gz
initial commit
Diffstat (limited to 'cart.c')
-rw-r--r--cart.c261
1 files changed, 261 insertions, 0 deletions
diff --git a/cart.c b/cart.c
new file mode 100644
index 0000000..09c1c12
--- /dev/null
+++ b/cart.c
@@ -0,0 +1,261 @@
+/* CART header support library */
+
+#include <string.h>
+#include <stdio.h>
+#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 */
+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)" },
+};
+
+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);
+}
+