diff options
-rw-r--r-- | DOSes.txt | 49 | ||||
-rw-r--r-- | Makefile | 82 | ||||
-rw-r--r-- | bank3.s | 9 | ||||
-rw-r--r-- | bank7.s | 85 | ||||
-rw-r--r-- | cart.txt | 141 | ||||
-rw-r--r-- | gzip2deflate.c | 78 | ||||
-rw-r--r-- | mkcart.1 | 142 | ||||
-rw-r--r-- | mkcart.c | 678 | ||||
-rw-r--r-- | mkcart.rst | 111 | ||||
-rw-r--r-- | newtitle.s | 8 | ||||
-rw-r--r-- | portstat.s | 1 | ||||
-rw-r--r-- | soundasm.s | 2 | ||||
-rw-r--r-- | taipan.c | 19 |
13 files changed, 1393 insertions, 12 deletions
diff --git a/DOSes.txt b/DOSes.txt new file mode 100644 index 0000000..5bb1308 --- /dev/null +++ b/DOSes.txt @@ -0,0 +1,49 @@ + +Taipan's intended target is really an 800 with an 810 drive and DOS +2.0S. However, most people use something else these days. + +There are only two XL/XE specific things in Taipan: + +- The checkmem code attempts to disable built-in BASIC if + it's enabled, which won't work on an 800 (you get a "remove + cartridge" message instead) + +- Keyclicks are disabled on XL/XE, with POKE 731,1. This doesn't + work on an 800, but doesn't hurt anything either (you just get + the game with keyclicks enabled) + +Unfortunately, the wide variety of Atari DOSes, both ancient and modern, +plus the various new bits of hardware (MyIDE, Atarimax Flash cart, etc) +leads to a compatibility nightmare. The rest of this file is a list of +DOSes (and other loaders), and their compatibility with Taipan. + +Tested on actual hardware: + +- Atari DOS 2.0S: success +- Atari DOS 2.5: success +- MyDOS 4.50 (floppy): success +- MyDOS (with MyIDE): success (but what version?) +- SpartaDOS X: success, with "X" command +- SpartaDOS 3.2dx: fails to load. I think "3.2dx" might be a typo, it + might be "3.2d"? +- SIDE2: success (older build though, need to get results for latest) +- APE loader: success (older build, need newer results) +- DOS XL 2.30: success + +Tested on emulators: + +- atari800 3.1.0 direct xex loader: success +- atari++ direct xex loader: success +- altirra direct xex loader: success +- Fenders 3-sector loader on atari800: success +- SpartaDOS 3.2g on atari800: success (unexpectedly so) +- Atari DOS 3.0: success + +Untested (waiting on results from testers): + +- Atarimax flash cart EXE loader + +Untested, but worth looking at: + +- MyPicoDOS +- RealDOS, BeweDOS? probably same results as Sparta 3... @@ -61,7 +61,7 @@ STACK_SIZE=0x200 # and the later github cc65, so it's been removed here. #CFLAGS=-t $(SYS) -T -I. -L. -Wl -D__SYSTEM_CHECK__=1 -Wl -D__RESERVED_MEMORY__=1056 $(COPT) -CFLAGS=-t $(SYS) -T -I. -L. -Wl -D__SYSTEM_CHECK__=1 -DFONT_ADDR=$(FONT_ADDR) --start-addr $(TAIMAIN_ADDR) -Wl -D__STACKSIZE__=$(STACK_SIZE) $(COPT) +CFLAGS=-t $(SYS) -T -I. -L. -Wl -D__SYSTEM_CHECK__=1 -DFONT_ADDR=$(FONT_ADDR) --start-addr $(TAIMAIN_ADDR) -Wl -D__STACKSIZE__=$(STACK_SIZE) $(COPT) $(EXTRACFLAGS) AS=ca65 ASFLAGS= @@ -240,6 +240,66 @@ PORTSTAT.DAT: mkportstats.xex convfont: convfont.c $(HOSTCC) $(HOSTCFLAGS) -DFONT_ADDR=$(FONT_ADDR) -o convfont convfont.c +# Cartridge-related targets. Build as a cart isn't really supported yet. + +# mkcart turns a raw binary into an atar800 .cart image with 16-byte header. +mkcart: mkcart.c + $(HOSTCC) $(HOSTCFLAGS) -o mkcart mkcart.c + +# cc65 doc atari.html explains how to produce a raw binary file. +# we build it for $03ff because cc65-produced atari binaries always +# have an RTS as the first byte (for compatibility with SpartaDOS). +# the "tail -c+2" stuff removes the first byte, so we end up with +# a romable_taimain.raw that's ready to be copied to $0400 and run +# via "JSR $0400". +romable_taimain.raw: $(TAIMAIN_C_SRC) $(TAIMAIN_ASM_SRC) $(TAIMAIN_HDRS) + rm -f taimain.xex + $(MAKE) TAIMAIN_ADDR=0x3ff EXTRACFLAGS="-Wl -D__AUTOSTART__=1 -Wl -D__EXEHDR__=1 -DCART_TARGET=1 --asm-define CART_TARGET=1" taimain.xex + tail -c+2 taimain.xex > romable_taimain.raw + rm -f taimain.xex + +# 512 bytes of $ff filler, for the last page of each code bank. wasting +# this little bit of space simplifies the copying code in bank7.s, and +# guarantees I don't accidentally end up with a 0 in the "cart present" +# byte of the cart trailer. +fill512: + perl -Mbytes -e 'print chr(0xff) x 512' > fill512 + +# 8192 bytes of $ff filler, for unused banks. Possibly these will be +# used for something like an interactive game manual/tutorial. +blankbank: + perl -Mbytes -e 'print chr(0xff) x 8192' > blankbank + +splitrom.raw.0: +splitrom.raw.1: +splitrom.raw.2: +splitrom.raw.3: + split -b 7680 -a 1 -d romable_taimain.raw splitrom.raw. + +bank0: splitrom.raw.0 fill512 + cat splitrom.raw.0 fill512 > bank0 + +bank1: splitrom.raw.1 fill512 + cat splitrom.raw.1 fill512 > bank1 + +bank2: splitrom.raw.2 fill512 + cat splitrom.raw.2 fill512 > bank2 + +bank3: splitrom.raw.3 bank3.s taifont + cl65 -l bank3.lst -m bank3.map -t none -o bank3 bank3.s + +bank7: bank7.s titledata.dat + cl65 -l bank7.lst -m bank7.map -t none -o bank7 bank7.s + +taipan.rom: bank0 bank1 bank2 bank3 bank7 blankbank + cat bank0 bank1 bank2 bank3 blankbank blankbank blankbank bank7 > taipan.rom + +cart: taipan.cart + +taipan.cart: taipan.rom mkcart + ./mkcart -otaipan.cart -t13 taipan.rom + ./mkcart -ctaipan.cart + # Rules for building various file types with the cc65 toolchain. .s.o: $(AS) $(ASFLAGS) -o $@ $< @@ -269,6 +329,26 @@ lorchatest: lorchatest.c draw_lorcha.s taifont.xex cat taifont.xex lorchatest1.xex > lorchatest.xex atari800 -nobasic lorchatest.xex +#### cruft, from when I was planning to use a 32K cart: +# this was a blind alley: zlib is too slow to decompress, plus there's +# no need to compress taimain.xex since I'm able to use a 64K cart. + +# gzip2deflate downloaded from https://github.com/pfusik/zlib6502 +# I could have used deflator.c that ships with cc65's source, but +# it's deprecated by its own upstream (same author as gzip2deflate). +gzip2deflate: gzip2deflate.c + $(HOSTCC) $(HOSTCFLAGS) -o gzip2deflate gzip2deflate.c + +zlibtest.xex: gzip2deflate zlibtest.c zlibtestdata.s romable_taimain.raw + gzip -9c < romable_taimain.raw | ./gzip2deflate > rom.dfl + $(CC) -t atari -m zlibtest.map -l zlibtest.lst -Wl -D__SYSTEM_CHECK__=1 --start-addr 0x7000 -o zlibtest.xex zlibtest.c zlibtestdata.s +romable_taimain.xex: $(TAIMAIN_C_SRC) $(TAIMAIN_ASM_SRC) $(TAIMAIN_HDRS) + rm -f taimain.xex + $(MAKE) TAIMAIN_ADDR=0x3ff EXTRACFLAGS="-DCART_TARGET=1 --asm-define CART_TARGET=1" + mv taimain.xex romable_taimain.xex + +### + soundtest: sounds.c cl65 -DTESTXEX -t atari -o sounds.xex sounds.c atari800 -nobasic sounds.xex @@ -0,0 +1,9 @@ + + .include "atari.inc" + +font = $9c00 + + .org $8000 + .incbin "splitrom.raw.3" + .res font - *, $ff + .incbin "taifont" @@ -0,0 +1,85 @@ + + .include "atari.inc" + +font = $9c00 +CCNTL = $d500 +destptr = FR0 +srcptr = FR1 +codedest = $0400 + +cart_trailer = $bffa + .org $a000 ; left cartridge + +titledata: + .incbin "titledata.dat" + +dlist: + +; copy 7680 bytes from $8000-$9dff to (destptr). +; on exit, destptr points to the next 7680 byte chunk. +copy_30_pages: + lda #$0 + tay + sta srcptr + lda #$80 + sta srcptr+1 + ldx #$1e +@copypage: + lda (srcptr),y + sta (destptr),y + dey + bne @copypage + inc srcptr+1 + inc destptr+1 + dex + bne @copypage +init: + rts + +start: +; turn off ANTIC DMA to speed up copying to RAM + lda #0 + sta SDMCTL + sta DMACTL + +; copy code to RAM + lda #<codedest + sta destptr + lda #>codedest + sta destptr+1 + lda #0 ; bank 0... + sta CCNTL ; ...select it + jsr copy_30_pages + lda #1 ; bank 1... + sta CCNTL ; ...select it + jsr copy_30_pages + lda #2 ; bank 2... + sta CCNTL ; ...select it + jsr copy_30_pages + lda #3 ; bank 3... + sta CCNTL ; ...select it + jsr copy_30_pages + + ; leave bank 3 enabled, as it has our custom font in it + +; set up display list + lda #34 + sta SDMCTL + lda #>font + sta CHBAS + ;lda $ff + ;sta CH +;@wait4key: + ;cmp CH + ;beq @wait4key + jsr $0400 + + .if * > cart_trailer + .fatal "bank7 code too large" + .endif + + .res cart_trailer - *, $ff + .word start ; entry point + .byte 0 ; 0 = cartridge present + .byte 4 ; init and run the cart, don't boot the disk, non-diagnostic + .word init ; init address (just an RTS) diff --git a/cart.txt b/cart.txt new file mode 100644 index 0000000..a7783da --- /dev/null +++ b/cart.txt @@ -0,0 +1,141 @@ + +What's needed to get taipan onto a cart: + +joey_z is willing to manufacture carts like this: + ++---------------------------------------------------------------------------+ +| Type 13: XEGS 64 KB cartridge (banks 0-7) | ++---------------------------------------------------------------------------+ + + One of the two variants of the 64 KB XEGS cartridge, that's built on either + a C100649 board with the W1 solder point not connected, or a C026449 board + with pin 9 of the 74LS374 chip unconnected. + This bank-switched cartridge occupies 16 KB of address space between $8000 + and $BFFF. The cartridge memory is divided into 8 banks, 8 KB each. + Bank 7 (the last one) is always mapped to $A000-$BFFF. Three lowest bits of + a byte written to $D500-$D5FF select the bank mapped to $8000-$9FFF. + The initially selected bank is random, although it seems that 0 gets chosen + the most often. Atari800 always selects bank 0 initially. + + Reference: + http://www.atarimax.com/jindroush.atari.org/acarts.html#xegs + +"For bank-switched cartridges banks are numbered in the order they appear +in the image file, starting with 0." + +...so: + +Bank 7 will have the startup code, uncompressed title screen, title +code, and code to copy from the other banks to RAM. + +That gives me banks 0-6 to store code in. The code is 27174 bytes, so I +have plenty of space: it'll occupy 4 banks, leaving 2 empty ones. The +last code bank will only be 1/3 full of code... but 1K of it will be +used for the font, so 4.5K free there. + +Code in bank 7 will copy all the chunks to correct place in RAM... and +I don't need to leave room for DOS or anything else, so the code can be +ORGed at $0400 (romable_taimain.raw target in the Makefile does this). + +$0400 + 27174 means the code ends at $6e26, and the BSS is less than a +page. The OS will place the GR.0 display list at $7c20, and the stack +will grow down from there to $7a40 (except it never grows that much). + +Copying the code to RAM will take some time, but not too much. Should be +around 1/4 second, don't need a progress bar or anything. Not sure when to do +the copying: + +1. At boot, before displaying title screen? +2. After the title screen is displayed, before the menu is active? +3. After the player presses space/enter to start the game? + +Options 1 and 3 will allow turning off ANTIC DMA during the copy, making +it a bit faster. Option 2 gives the player something to look at while +the copy is happening, but I think it'll be so quick as to not matter. + +Amusingly, the Taipan cart will work on a 32K 800. Not a 16K Atari though. + +bank 7: fixed bank +$a000-$b9xx - title screen data, dl, menu code +$ba00-$bff9 - memory checker plus code to copy romable_taimain to RAM +$bffa-$bfff - cart trailer + +banks 0, 1, 2: full banks of romable_taimain code +$8000-$9dff - 30 pages (7680 bytes) of code +$9f00-$9fff - unused + +bank 3: last (partial) bank of romable_taimain, plus the font +$8000-$9bff - up to 7K of code (28 pages) +$9c00-$9fff - font (1K) + +Unused areas are filled with $ff. For the banks that map at $8000, this +includes the cart trailer area. A non-zero byte (our $ff) at $9ffc tells +the OS that a cart isn't inserted in the right slot, so it won't try to +initialize/run our cart as a right cart. Only bank 7 (that maps as a +left cart) needs a valid cart trailer... according to cart.txt, every +once in a while, bank 7 might come up selected at power on. This shouldn't +matter: it'll be in both bank areas, and if the OS tries to init it as +a right cart, the init/run addresses will point to the left cart area. + +banks 4, 5, 6 are unused (24K total). Possibly the manual goes here, +if I write one. + +-- + +Changes the game will need for a cart version: Not many. + +checkmem.s won't be needed any longer... though there will need to be +a new memory checker (in bank 7) that says "32K required" if someone +tries it on a 16K machine. + +"Play again?" should probably be "Press any key to play again" since +there's no place to exit() to. Unless I do a manual! Then it'll exit +to the title screen, from whence the user can choose the manual or else +start the game again. + +The title decompression will be gone: it'll just display the title screen +DL and data straight from ROM. The menu help text might be in RAM though, +as at least 2 bytes need to be modified (sound on/off). + +The font will be located in ROM, on a 1K boundary, so CHBAS can point +to it (no need to copy to RAM). + +num_buf and firm will be located in the BSS rather than page 6 as they +are in the .xex version. + +Since I have 3 empty banks... Why not include a manual on the cart, +with pseudo-hypertext UI? + +-- + +Cart header (trailer, actually). Can I get away with using the same one +for all banks? IIRC, the low one ("right cartridge") gets looked at first, +does that mean the high bank doesn't even need a header? + +atari800 can load a .xex that's built with --start-addr 0x400. This will +make testing the cart stuff slightly easier. + +-- + +What would be *really* slick: figure out a way to split the code up +across banks and include bankswitching in the logic, so it runs from +the cart and switches banks as needed. This would allow Taipan to run +on a 16K or even an 8K 400/800! + +Can it be done? Surely. Can it be done without rewriting everything +in asm? Probably. Do I want to? Not really. + +-- + +The cartridge label should look like a classic brown 1st-gen 400/800 cart. +Yellow text: + +TAIPAN! +Computer Game + +and a bogus serial number.. CXU000001 or such (U for Urchlay). + +The top should say LEFT CARTRIDGE. + +If there's going to be a printed manual, it should be based on the Apple II +version's manual. If there was one. If I can find a copy. diff --git a/gzip2deflate.c b/gzip2deflate.c new file mode 100644 index 0000000..7edf6ac --- /dev/null +++ b/gzip2deflate.c @@ -0,0 +1,78 @@ +// gzip2deflate by Piotr Fusik <fox@scene.pl> +// http://xasm.atari.org + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef __WIN32 +#include <fcntl.h> +#endif + +static int get_byte(void) +{ + int b = getchar(); + if (b == EOF) { + fputs("gzip2deflate: unexpected end of stream\n", stderr); + exit(1); + } + return b; +} + +static void skip_bytes(int n) +{ + while (--n >= 0) + get_byte(); +} + +static void skip_string(void) +{ + while (get_byte() != 0); +} + +int main (int argc, char *argv[]) +{ + int flg; + char buf[8 + 4096]; + size_t len; +#ifdef __WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + if (get_byte() != 0x1f || get_byte() != 0x8b || get_byte() != 8) { + fputs("gzip2deflate: not a gzip file on stdin\n", stderr); + return 1; + } + flg = get_byte(); + skip_bytes(6); + if ((flg & 4) != 0) { + int xlen = get_byte(); + xlen += get_byte() << 8; + skip_bytes(xlen); + } + if ((flg & 8) != 0) + skip_string(); + if ((flg & 16) != 0) + skip_string(); + if ((flg & 2) != 0) + skip_bytes(2); + /* copy everything except the last 8 bytes */ + len = 0; + for (;;) { + len += fread(buf + len, 1, sizeof(buf) - len, stdin); + if (len != sizeof(buf)) + break; + fwrite(buf, 1, sizeof(buf) - 8, stdout); + memcpy(buf, buf + sizeof(buf) - 8, 8); + len = 8; + } + if (ferror(stdin)) { + fputs("gzip2deflate: read error\n", stderr); + return 1; + } + if (len < 8) { + fputs("gzip2deflate: unexpected end of stream\n", stderr); + return 1; + } + fwrite(buf, 1, len - 8, stdout); + return 0; +} diff --git a/mkcart.1 b/mkcart.1 new file mode 100644 index 0000000..f957b33 --- /dev/null +++ b/mkcart.1 @@ -0,0 +1,142 @@ +.\" Man page generated from reStructuredText. +. +.TH MKCART 1 "2015-04-22" "2.10.12" "DASM-Dillon" +.SH NAME +mkcart \- Convert between raw ROM images and atari800 CART format +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH SYNOPSIS +.sp +mkcart \-oCARTFILE \-tTYPE RAWFILE [RAWFILE ...] +.sp +mkcart \-cCARTFILE +.sp +mkcart \-xRAWFILE CARTFILE +.sp +mkcart \-l +.SH DESCRIPTION +.sp +A companion tool to \fIdasm(1)\fP, mkcart can: +.INDENT 0.0 +.IP \(bu 2 +convert one or more \fIdasm(1)\fP raw (\-f3) object files to a CART image +format for use with emulators such as \fIatari800(1)\fP\&. +.IP \(bu 2 +convert a CART image back to a raw image. +.IP \(bu 2 +check the integrity and report information about a CART image. +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.BI \-t\fB TYPE +Cartridge type (1\-67, see \fI\-l\fP for list), default = guess (poorly!). +Only used in \-o mode. +.TP +.BI \-o\fB CARTFILE +Create CARTFILE from RAWFILE(s). \fI\-t\fP optional but highly +recommended. +.TP +.BI \-c\fB CARTFILE +Check integrity of file (header, checksum, and size). +.TP +.BI \-x\fB RAWFILE +Create raw binary from CARTFILE (remove header). +.TP +.B \-l +List all supported \-t types with their sizes in +bytes and their human\-readable names, and exit. +.UNINDENT +.sp +\-?, \-h Show built\-in help message. +.SH EXAMPLES +.nf +# a standard 8KB cartridge: +dasm example.asm \-f3 \-oexample.bin +mkcart \-oexample.cart \-t1 example.bin +.fi +.sp +.nf +# a bankswitched OSS 16KB cartridge: +dasm bank1.asm \-f3 \-obank1.bin +dasm bank2.asm \-f3 \-obank2.bin +mkcart \-oexample.cart \-t15 bank1.bin bank2.bin +.fi +.sp +.SH EXIT CODES +.sp +With \-o and \-x, mkcart will exit with status \fI0\fP if it was able +to complete the conversion, or \fI1\fP if something went wrong. +.sp +With \-c, mkcart will exit with status \fI0\fP if the image is OK (has a +valid header, known type, good checksum, etc), or \fI1\fP if not. +.SH BUGS +.sp +With \-o and \-x, the input files are opened, read, and closed twice: +once to calculate the checksum and verify that there\(aqs enough data, then +the header is written and the input files are re\-opened and reread. Bad +Things will probably happen if any of the input files change in between +the two passes. +.sp +The \-x option should split bankswitched cartridges into multiple raw +images instead of one combined image. Workaround: use \fIsplit(1)\fP +or \fIdd(1)\fP to split the raw image into bank\-sized chunks: +.nf +mkcart \-xmac65.bin mac65.cart +.fi +.sp +.nf +# split into mac65.bank00 and mac65.bank01 +split \-b8192 mac65.bin mac65.bank +.fi +.sp +.nf +# same thing, with dd +dd if=mac65.bin of=mac65.bank00 bs=1024 count=8 +dd if=mac65.bin of=mac65.bank01 bs=1024 count=8 skip=8 +.fi +.sp +.sp +Either way, you have to know the bank size (usually 8 or 16 KB), which +is less than ideal. +.SH SEE ALSO +.INDENT 0.0 +.IP \(bu 2 +\fIdasm(1)\fP +.IP \(bu 2 +\fIatari800(1)\fP +.IP \(bu 2 +\fIcart.txt\fP +.UNINDENT +.SH AUTHOR +B. Watson <yalhcru@gmail.com> +.SH COPYRIGHT +This document is licensed under the same terms as DASM +itself, see the file COPYING for details. +.\" Generated by docutils manpage writer. +. diff --git a/mkcart.c b/mkcart.c new file mode 100644 index 0000000..28ba5d5 --- /dev/null +++ b/mkcart.c @@ -0,0 +1,678 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +/* + mkcart.c, by B. Watson, part of DASM Atari 8-bit support. + + DASM and atari800 are both GPLv2 so I've lifted code straight + from the emulator. + + This is not great C code. It's what happens when I try to write + C after spending a week hacking assembly code. + */ + +/* nobody needs more input files than this, right? I should + define it to 640K :) */ +#define MAX_INPUT_FILES 1024 + +/* this is the smallest supported cart size */ +#define CARTBUFLEN 2048 + +typedef struct { + char *name; + int size; +} cart_t; + +/* from atari800-3.1.0/src/cartridge.h */ +enum { + CARTRIDGE_UNKNOWN = -1, + CARTRIDGE_NONE = 0, + CARTRIDGE_STD_8 = 1, + CARTRIDGE_STD_16 = 2, + CARTRIDGE_OSS_034M_16 = 3, + CARTRIDGE_5200_32 = 4, + CARTRIDGE_DB_32 = 5, + CARTRIDGE_5200_EE_16 = 6, + CARTRIDGE_5200_40 = 7, + CARTRIDGE_WILL_64 = 8, + CARTRIDGE_EXP_64 = 9, + CARTRIDGE_DIAMOND_64 = 10, + CARTRIDGE_SDX_64 = 11, + CARTRIDGE_XEGS_32 = 12, + CARTRIDGE_XEGS_07_64 = 13, + CARTRIDGE_XEGS_128 = 14, + CARTRIDGE_OSS_M091_16 = 15, + CARTRIDGE_5200_NS_16 = 16, + CARTRIDGE_ATRAX_128 = 17, + CARTRIDGE_BBSB_40 = 18, + CARTRIDGE_5200_8 = 19, + CARTRIDGE_5200_4 = 20, + CARTRIDGE_RIGHT_8 = 21, + CARTRIDGE_WILL_32 = 22, + CARTRIDGE_XEGS_256 = 23, + CARTRIDGE_XEGS_512 = 24, + CARTRIDGE_XEGS_1024 = 25, + CARTRIDGE_MEGA_16 = 26, + CARTRIDGE_MEGA_32 = 27, + CARTRIDGE_MEGA_64 = 28, + CARTRIDGE_MEGA_128 = 29, + CARTRIDGE_MEGA_256 = 30, + CARTRIDGE_MEGA_512 = 31, + CARTRIDGE_MEGA_1024 = 32, + CARTRIDGE_SWXEGS_32 = 33, + CARTRIDGE_SWXEGS_64 = 34, + CARTRIDGE_SWXEGS_128 = 35, + CARTRIDGE_SWXEGS_256 = 36, + CARTRIDGE_SWXEGS_512 = 37, + CARTRIDGE_SWXEGS_1024 = 38, + CARTRIDGE_PHOENIX_8 = 39, + CARTRIDGE_BLIZZARD_16 = 40, + CARTRIDGE_ATMAX_128 = 41, + CARTRIDGE_ATMAX_1024 = 42, + CARTRIDGE_SDX_128 = 43, + CARTRIDGE_OSS_8 = 44, + CARTRIDGE_OSS_043M_16 = 45, + CARTRIDGE_BLIZZARD_4 = 46, + CARTRIDGE_AST_32 = 47, + CARTRIDGE_ATRAX_SDX_64 = 48, + CARTRIDGE_ATRAX_SDX_128 = 49, + CARTRIDGE_TURBOSOFT_64 = 50, + CARTRIDGE_TURBOSOFT_128 = 51, + CARTRIDGE_ULTRACART_32 = 52, + CARTRIDGE_LOW_BANK_8 = 53, + CARTRIDGE_SIC_128 = 54, + CARTRIDGE_SIC_256 = 55, + CARTRIDGE_SIC_512 = 56, + CARTRIDGE_STD_2 = 57, + CARTRIDGE_STD_4 = 58, + CARTRIDGE_RIGHT_4 = 59, + CARTRIDGE_BLIZZARD_32 = 60, + CARTRIDGE_MEGAMAX_2048 = 61, + CARTRIDGE_THECART_128M = 62, + CARTRIDGE_MEGA_4096 = 63, + CARTRIDGE_MEGA_2048 = 64, + CARTRIDGE_THECART_32M = 65, + CARTRIDGE_THECART_64M = 66, + CARTRIDGE_XEGS_8F_64 = 67, + CARTRIDGE_LAST_SUPPORTED = 67 +}; + +#define CARTRIDGE_MAX_SIZE (128 * 1024 * 1024) + +#define CARTRIDGE_STD_8_DESC "Standard 8 KB cartridge" +#define CARTRIDGE_STD_16_DESC "Standard 16 KB cartridge" +#define CARTRIDGE_OSS_034M_16_DESC "OSS two chip 16 KB cartridge (034M)" +#define CARTRIDGE_5200_32_DESC "Standard 32 KB 5200 cartridge" +#define CARTRIDGE_DB_32_DESC "DB 32 KB cartridge" +#define CARTRIDGE_5200_EE_16_DESC "Two chip 16 KB 5200 cartridge" +#define CARTRIDGE_5200_40_DESC "Bounty Bob 40 KB 5200 cartridge" +#define CARTRIDGE_WILL_64_DESC "64 KB Williams cartridge" +#define CARTRIDGE_EXP_64_DESC "Express 64 KB cartridge" +#define CARTRIDGE_DIAMOND_64_DESC "Diamond 64 KB cartridge" +#define CARTRIDGE_SDX_64_DESC "SpartaDOS X 64 KB cartridge" +#define CARTRIDGE_XEGS_32_DESC "XEGS 32 KB cartridge" +#define CARTRIDGE_XEGS_07_64_DESC "XEGS 64 KB cartridge (banks 0-7)" +#define CARTRIDGE_XEGS_128_DESC "XEGS 128 KB cartridge" +#define CARTRIDGE_OSS_M091_16_DESC "OSS one chip 16 KB cartridge" +#define CARTRIDGE_5200_NS_16_DESC "One chip 16 KB 5200 cartridge" +#define CARTRIDGE_ATRAX_128_DESC "Atrax 128 KB cartridge" +#define CARTRIDGE_BBSB_40_DESC "Bounty Bob 40 KB cartridge" +#define CARTRIDGE_5200_8_DESC "Standard 8 KB 5200 cartridge" +#define CARTRIDGE_5200_4_DESC "Standard 4 KB 5200 cartridge" +#define CARTRIDGE_RIGHT_8_DESC "Right slot 8 KB cartridge" +#define CARTRIDGE_WILL_32_DESC "32 KB Williams cartridge" +#define CARTRIDGE_XEGS_256_DESC "XEGS 256 KB cartridge" +#define CARTRIDGE_XEGS_512_DESC "XEGS 512 KB cartridge" +#define CARTRIDGE_XEGS_1024_DESC "XEGS 1 MB cartridge" +#define CARTRIDGE_MEGA_16_DESC "MegaCart 16 KB cartridge" +#define CARTRIDGE_MEGA_32_DESC "MegaCart 32 KB cartridge" +#define CARTRIDGE_MEGA_64_DESC "MegaCart 64 KB cartridge" +#define CARTRIDGE_MEGA_128_DESC "MegaCart 128 KB cartridge" +#define CARTRIDGE_MEGA_256_DESC "MegaCart 256 KB cartridge" +#define CARTRIDGE_MEGA_512_DESC "MegaCart 512 KB cartridge" +#define CARTRIDGE_MEGA_1024_DESC "MegaCart 1 MB cartridge" +#define CARTRIDGE_SWXEGS_32_DESC "Switchable XEGS 32 KB cartridge" +#define CARTRIDGE_SWXEGS_64_DESC "Switchable XEGS 64 KB cartridge" +#define CARTRIDGE_SWXEGS_128_DESC "Switchable XEGS 128 KB cartridge" +#define CARTRIDGE_SWXEGS_256_DESC "Switchable XEGS 256 KB cartridge" +#define CARTRIDGE_SWXEGS_512_DESC "Switchable XEGS 512 KB cartridge" +#define CARTRIDGE_SWXEGS_1024_DESC "Switchable XEGS 1 MB cartridge" +#define CARTRIDGE_PHOENIX_8_DESC "Phoenix 8 KB cartridge" +#define CARTRIDGE_BLIZZARD_16_DESC "Blizzard 16 KB cartridge" +#define CARTRIDGE_ATMAX_128_DESC "Atarimax 128 KB Flash cartridge" +#define CARTRIDGE_ATMAX_1024_DESC "Atarimax 1 MB Flash cartridge" +#define CARTRIDGE_SDX_128_DESC "SpartaDOS X 128 KB cartridge" +#define CARTRIDGE_OSS_8_DESC "OSS 8 KB cartridge" +#define CARTRIDGE_OSS_043M_16_DESC "OSS two chip 16 KB cartridge (043M)" +#define CARTRIDGE_BLIZZARD_4_DESC "Blizzard 4 KB cartridge" +#define CARTRIDGE_AST_32_DESC "AST 32 KB cartridge" +#define CARTRIDGE_ATRAX_SDX_64_DESC "Atrax SDX 64 KB cartridge" +#define CARTRIDGE_ATRAX_SDX_128_DESC "Atrax SDX 128 KB cartridge" +#define CARTRIDGE_TURBOSOFT_64_DESC "Turbosoft 64 KB cartridge" +#define CARTRIDGE_TURBOSOFT_128_DESC "Turbosoft 128 KB cartridge" +#define CARTRIDGE_ULTRACART_32_DESC "Ultracart 32 KB cartridge" +#define CARTRIDGE_LOW_BANK_8_DESC "Low bank 8 KB cartridge" +#define CARTRIDGE_SIC_128_DESC "SIC! 128 KB cartridge" +#define CARTRIDGE_SIC_256_DESC "SIC! 256 KB cartridge" +#define CARTRIDGE_SIC_512_DESC "SIC! 512 KB cartridge" +#define CARTRIDGE_STD_2_DESC "Standard 2 KB cartridge" +#define CARTRIDGE_STD_4_DESC "Standard 4 KB cartridge" +#define CARTRIDGE_RIGHT_4_DESC "Right slot 4 KB cartridge" +#define CARTRIDGE_BLIZZARD_32_DESC "Blizzard 32 KB cartridge" +#define CARTRIDGE_MEGAMAX_2048_DESC "MegaMax 2 MB cartridge" +#define CARTRIDGE_THECART_128M_DESC "The!Cart 128 MB cartridge" +#define CARTRIDGE_MEGA_4096_DESC "Flash MegaCart 4 MB cartridge" +#define CARTRIDGE_MEGA_2048_DESC "MegaCart 2 MB cartridge" +#define CARTRIDGE_THECART_32M_DESC "The!Cart 32 MB cartridge" +#define CARTRIDGE_THECART_64M_DESC "The!Cart 64 MB cartridge" +#define CARTRIDGE_XEGS_8F_64_DESC "XEGS 64 KB cartridge (banks 8-15)" + +/* this bit didn't come from atari800 */ +static cart_t cart_types[CARTRIDGE_LAST_SUPPORTED + 1]; +#define UI_MENU_ACTION(index, desc) \ + cart_types[index].size = CARTRIDGE_kb[index]*1024; \ + cart_types[index].name = desc; + +/* from atari800-3.1.0/src/cartridge.c */ +int const CARTRIDGE_kb[CARTRIDGE_LAST_SUPPORTED + 1] = { + 0, + 8, /* CARTRIDGE_STD_8 */ + 16, /* CARTRIDGE_STD_16 */ + 16, /* CARTRIDGE_OSS_034M_16 */ + 32, /* CARTRIDGE_5200_32 */ + 32, /* CARTRIDGE_DB_32 */ + 16, /* CARTRIDGE_5200_EE_16 */ + 40, /* CARTRIDGE_5200_40 */ + 64, /* CARTRIDGE_WILL_64 */ + 64, /* CARTRIDGE_EXP_64 */ + 64, /* CARTRIDGE_DIAMOND_64 */ + 64, /* CARTRIDGE_SDX_64 */ + 32, /* CARTRIDGE_XEGS_32 */ + 64, /* CARTRIDGE_XEGS_64_07 */ + 128, /* CARTRIDGE_XEGS_128 */ + 16, /* CARTRIDGE_OSS_M091_16 */ + 16, /* CARTRIDGE_5200_NS_16 */ + 128, /* CARTRIDGE_ATRAX_128 */ + 40, /* CARTRIDGE_BBSB_40 */ + 8, /* CARTRIDGE_5200_8 */ + 4, /* CARTRIDGE_5200_4 */ + 8, /* CARTRIDGE_RIGHT_8 */ + 32, /* CARTRIDGE_WILL_32 */ + 256, /* CARTRIDGE_XEGS_256 */ + 512, /* CARTRIDGE_XEGS_512 */ + 1024, /* CARTRIDGE_XEGS_1024 */ + 16, /* CARTRIDGE_MEGA_16 */ + 32, /* CARTRIDGE_MEGA_32 */ + 64, /* CARTRIDGE_MEGA_64 */ + 128, /* CARTRIDGE_MEGA_128 */ + 256, /* CARTRIDGE_MEGA_256 */ + 512, /* CARTRIDGE_MEGA_512 */ + 1024, /* CARTRIDGE_MEGA_1024 */ + 32, /* CARTRIDGE_SWXEGS_32 */ + 64, /* CARTRIDGE_SWXEGS_64 */ + 128, /* CARTRIDGE_SWXEGS_128 */ + 256, /* CARTRIDGE_SWXEGS_256 */ + 512, /* CARTRIDGE_SWXEGS_512 */ + 1024, /* CARTRIDGE_SWXEGS_1024 */ + 8, /* CARTRIDGE_PHOENIX_8 */ + 16, /* CARTRIDGE_BLIZZARD_16 */ + 128, /* CARTRIDGE_ATMAX_128 */ + 1024, /* CARTRIDGE_ATMAX_1024 */ + 128, /* CARTRIDGE_SDX_128 */ + 8, /* CARTRIDGE_OSS_8 */ + 16, /* CARTRIDGE_OSS_043M_16 */ + 4, /* CARTRIDGE_BLIZZARD_4 */ + 32, /* CARTRIDGE_AST_32 */ + 64, /* CARTRIDGE_ATRAX_SDX_64 */ + 128, /* CARTRIDGE_ATRAX_SDX_128 */ + 64, /* CARTRIDGE_TURBOSOFT_64 */ + 128, /* CARTRIDGE_TURBOSOFT_128 */ + 32, /* CARTRIDGE_ULTRACART_32 */ + 8, /* CARTRIDGE_LOW_BANK_8 */ + 128, /* CARTRIDGE_SIC_128 */ + 256, /* CARTRIDGE_SIC_256 */ + 512, /* CARTRIDGE_SIC_512 */ + 2, /* CARTRIDGE_STD_2 */ + 4, /* CARTRIDGE_STD_4 */ + 4, /* CARTRIDGE_RIGHT_4 */ + 32, /* CARTRIDGE_TURBO_HIT_32 */ + 2048, /* CARTRIDGE_MEGA_2048 */ + 128*1024, /* CARTRIDGE_THECART_128M */ + 4096, /* CARTRIDGE_MEGA_4096 */ + 2048, /* CARTRIDGE_MEGA_2048 */ + 32*1024, /* CARTRIDGE_THECART_32M */ + 64*1024, /* CARTRIDGE_THECART_64M */ + 64 /* CARTRIDGE_XEGS_64_8F */ +}; + +/* Adapted from from atari800-3.1.0/src/ui.c, by s/,$/;/ */ +void init() { + UI_MENU_ACTION(CARTRIDGE_STD_8, CARTRIDGE_STD_8_DESC); + UI_MENU_ACTION(CARTRIDGE_STD_16, CARTRIDGE_STD_16_DESC); + UI_MENU_ACTION(CARTRIDGE_OSS_034M_16, CARTRIDGE_OSS_034M_16_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_32, CARTRIDGE_5200_32_DESC); + UI_MENU_ACTION(CARTRIDGE_DB_32, CARTRIDGE_DB_32_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_EE_16, CARTRIDGE_5200_EE_16_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_40, CARTRIDGE_5200_40_DESC); + UI_MENU_ACTION(CARTRIDGE_WILL_64, CARTRIDGE_WILL_64_DESC); + UI_MENU_ACTION(CARTRIDGE_EXP_64, CARTRIDGE_EXP_64_DESC); + UI_MENU_ACTION(CARTRIDGE_DIAMOND_64, CARTRIDGE_DIAMOND_64_DESC); + UI_MENU_ACTION(CARTRIDGE_SDX_64, CARTRIDGE_SDX_64_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_32, CARTRIDGE_XEGS_32_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_07_64, CARTRIDGE_XEGS_07_64_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_128, CARTRIDGE_XEGS_128_DESC); + UI_MENU_ACTION(CARTRIDGE_OSS_M091_16, CARTRIDGE_OSS_M091_16_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_NS_16, CARTRIDGE_5200_NS_16_DESC); + UI_MENU_ACTION(CARTRIDGE_ATRAX_128, CARTRIDGE_ATRAX_128_DESC); + UI_MENU_ACTION(CARTRIDGE_BBSB_40, CARTRIDGE_BBSB_40_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_8, CARTRIDGE_5200_8_DESC); + UI_MENU_ACTION(CARTRIDGE_5200_4, CARTRIDGE_5200_4_DESC); + UI_MENU_ACTION(CARTRIDGE_RIGHT_8, CARTRIDGE_RIGHT_8_DESC); + UI_MENU_ACTION(CARTRIDGE_WILL_32, CARTRIDGE_WILL_32_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_256, CARTRIDGE_XEGS_256_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_512, CARTRIDGE_XEGS_512_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_1024, CARTRIDGE_XEGS_1024_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_16, CARTRIDGE_MEGA_16_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_32, CARTRIDGE_MEGA_32_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_64, CARTRIDGE_MEGA_64_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_128, CARTRIDGE_MEGA_128_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_256, CARTRIDGE_MEGA_256_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_512, CARTRIDGE_MEGA_512_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_1024, CARTRIDGE_MEGA_1024_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_32, CARTRIDGE_SWXEGS_32_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_64, CARTRIDGE_SWXEGS_64_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_128, CARTRIDGE_SWXEGS_128_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_256, CARTRIDGE_SWXEGS_256_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_512, CARTRIDGE_SWXEGS_512_DESC); + UI_MENU_ACTION(CARTRIDGE_SWXEGS_1024, CARTRIDGE_SWXEGS_1024_DESC); + UI_MENU_ACTION(CARTRIDGE_PHOENIX_8, CARTRIDGE_PHOENIX_8_DESC); + UI_MENU_ACTION(CARTRIDGE_BLIZZARD_16, CARTRIDGE_BLIZZARD_16_DESC); + UI_MENU_ACTION(CARTRIDGE_ATMAX_128, CARTRIDGE_ATMAX_128_DESC); + UI_MENU_ACTION(CARTRIDGE_ATMAX_1024, CARTRIDGE_ATMAX_1024_DESC); + UI_MENU_ACTION(CARTRIDGE_SDX_128, CARTRIDGE_SDX_128_DESC); + UI_MENU_ACTION(CARTRIDGE_OSS_8, CARTRIDGE_OSS_8_DESC); + UI_MENU_ACTION(CARTRIDGE_OSS_043M_16, CARTRIDGE_OSS_043M_16_DESC); + UI_MENU_ACTION(CARTRIDGE_BLIZZARD_4, CARTRIDGE_BLIZZARD_4_DESC); + UI_MENU_ACTION(CARTRIDGE_AST_32, CARTRIDGE_AST_32_DESC); + UI_MENU_ACTION(CARTRIDGE_ATRAX_SDX_64, CARTRIDGE_ATRAX_SDX_64_DESC); + UI_MENU_ACTION(CARTRIDGE_ATRAX_SDX_128, CARTRIDGE_ATRAX_SDX_128_DESC); + UI_MENU_ACTION(CARTRIDGE_TURBOSOFT_64, CARTRIDGE_TURBOSOFT_64_DESC); + UI_MENU_ACTION(CARTRIDGE_TURBOSOFT_128, CARTRIDGE_TURBOSOFT_128_DESC); + UI_MENU_ACTION(CARTRIDGE_ULTRACART_32, CARTRIDGE_ULTRACART_32_DESC); + UI_MENU_ACTION(CARTRIDGE_LOW_BANK_8, CARTRIDGE_LOW_BANK_8_DESC); + UI_MENU_ACTION(CARTRIDGE_SIC_128, CARTRIDGE_SIC_128_DESC); + UI_MENU_ACTION(CARTRIDGE_SIC_256, CARTRIDGE_SIC_256_DESC); + UI_MENU_ACTION(CARTRIDGE_SIC_512, CARTRIDGE_SIC_512_DESC); + UI_MENU_ACTION(CARTRIDGE_STD_2, CARTRIDGE_STD_2_DESC); + UI_MENU_ACTION(CARTRIDGE_STD_4, CARTRIDGE_STD_4_DESC); + UI_MENU_ACTION(CARTRIDGE_RIGHT_4, CARTRIDGE_RIGHT_4_DESC); + UI_MENU_ACTION(CARTRIDGE_BLIZZARD_32, CARTRIDGE_BLIZZARD_32_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGAMAX_2048, CARTRIDGE_MEGAMAX_2048_DESC); + UI_MENU_ACTION(CARTRIDGE_THECART_128M, CARTRIDGE_THECART_128M_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_4096, CARTRIDGE_MEGA_4096_DESC); + UI_MENU_ACTION(CARTRIDGE_MEGA_2048, CARTRIDGE_MEGA_2048_DESC); + UI_MENU_ACTION(CARTRIDGE_THECART_32M, CARTRIDGE_THECART_32M_DESC); + UI_MENU_ACTION(CARTRIDGE_THECART_64M, CARTRIDGE_THECART_64M_DESC); + UI_MENU_ACTION(CARTRIDGE_XEGS_8F_64, CARTRIDGE_XEGS_8F_64_DESC); +} + +static int type = -1; /* -1 = guess */ +static int extracting = 0; +static const char *outfile = NULL; +static FILE *output; +static const char *inputfiles[MAX_INPUT_FILES+1]; +static int inputcount = 0; +static int checksum = 0; +static int keep_outfile = 0; +static unsigned char buf[CARTBUFLEN]; + +void list_types() { + int i; + for(i = 1; i <= CARTRIDGE_LAST_SUPPORTED; i++) { + printf("%d %d %s\n", i, cart_types[i].size, cart_types[i].name); + } +} + +void usage() { + puts("mkcart v20150421 - create atari800 CART image from raw binaries"); + puts("\nUsage: mkcart -oCARTFILE -tTYPE RAWFILE [RAWFILE ...]"); + puts( " mkcart -cCARTFILE"); + puts( " mkcart -xRAWFILE CARTFILE"); + puts( " mkcart -l"); + printf("\n -tTYPE Cartridge type (1-%d), default = guess (poorly!)\n", + CARTRIDGE_LAST_SUPPORTED); + puts( " -oCARTFILE Create CARTFILE from RAWFILE(s)"); + puts( " -cCARTFILE Check integrity of file (checksum and size)"); + puts( " -xRAWFILE Create raw binary from CARTFILE (remove header)"); + puts( " -l List all supported -t types and exit"); + puts( " -h, -? This help message"); +} + +void open_output() { + if(!outfile) { + fprintf(stderr, "No output file given, use -o option\n"); + exit(1); + } + if( !(output = fopen(outfile, "wb")) ) { + perror(outfile); + exit(1); + } +} + +FILE *open_input(const char *fname) { + FILE *f; + + f = fopen(fname, "rb"); + if(!f) { + perror(fname); + exit(1); + } + return f; +} + +int has_cart_header(const unsigned char *b) { + return ( (buf[0] == 'C') && + (buf[1] == 'A') && + (buf[2] == 'R') && + (buf[3] == 'T') ); +} + +void write_header() { + int i, j, size = 0, checkhdr; + FILE *f; + size_t got; + + for(i = 0; i < inputcount; i++) { + f = open_input(inputfiles[i]); + + if(extracting) { + /* read and check header insead of writing one */ + if(fread(buf, 1, 16, f) < 16) { + perror(inputfiles[i]); + exit(-1); + } + if(!has_cart_header(buf)) { + fprintf(stderr, "%s doesn't have a CART header\n", inputfiles[i]); + exit(-1); + } + return; + } + + checkhdr = 1; + + while( (got = fread(buf, 1, CARTBUFLEN, f)) > 0) { + if(checkhdr) { /* only do this on first chunk read */ + if(has_cart_header(buf)) { + fprintf(stderr, + "warning: raw file %s appears to have a CART header\n", + inputfiles[i]); + } + checkhdr = 0; + } + if(got < CARTBUFLEN) { + fprintf(stderr, "warning: %s size not a multiple of %d bytes\n", + inputfiles[i], CARTBUFLEN); + } + for(j = 0; j < got; j++) checksum += buf[j]; + size += got; + } + if(ferror(f)) { + perror(inputfiles[i]); + exit(1); + } + fclose(f); + } + + if(type > 0 && size != cart_types[type].size) { + fprintf(stderr, + "warning: cart type %d (%s) must be %d bytes, " + "but we read %d from our input files\n", + type, cart_types[type].name, cart_types[type].size, size); + } + + if(type < 1) { + for(i = 1; i <= CARTRIDGE_LAST_SUPPORTED; i++) { + if(size == (cart_types[i].size)) { + type = i; + fprintf(stderr, "warning: no -t option, guessing type %d (%s)\n", + i, cart_types[i].name); + break; + } + } + if(type < 1) { + fprintf(stderr, + "fatal: no -t option, no type matches file size %d bytes\n", + size); + exit(-1); + } + } + + /* more like assembly than C, but it avoids endian issues */ + buf[0] = 'C'; + buf[1] = 'A'; + buf[2] = 'R'; + buf[3] = 'T'; + buf[4] = buf[5] = buf[6] = 0; + buf[7] = type; + buf[8] = (checksum >> 24) & 0xff; + buf[9] = (checksum >> 16) & 0xff; + buf[10] = (checksum >> 8) & 0xff; + buf[11] = checksum & 0xff; + buf[12] = buf[13] = buf[14] = buf[15] = 0; + + i = fwrite(buf, 1, 16, output); + if(i < 0) { + perror(outfile); + exit(-1); + } else if(i < 16) { + fprintf(stderr, "short write on %s\n", outfile); + exit(-1); + } + /* leave output open here */ +} + +void write_data() { + int i; + FILE *f; + size_t got; + + for(i = 0; i < inputcount; i++) { + f = open_input(inputfiles[i]); + if(extracting) fread(buf, 1, 16, f); /* skip header */ + while( (got = fread(buf, 1, CARTBUFLEN, f)) > 0) { + if( (fwrite(buf, 1, got, output)) < got ) { + perror(outfile); + exit(-1); + } + } + if(ferror(f)) { + perror(inputfiles[i]); + exit(1); + } + fclose(f); + } + + /* if we made it here with no errors, the output file is good */ + keep_outfile = 1; +} + +void add_file(const char *filename) { + if(inputcount > MAX_INPUT_FILES) { + fprintf(stderr, "Too many input files (limit is %d, sorry)\n", + MAX_INPUT_FILES); + exit(1); + } + inputfiles[inputcount++] = filename; +} + +int extract4(const unsigned char *b) { + return ( (b[0] << 24) | + (b[1] << 16) | + (b[2] << 8) | + (b[3] ) ); +} + +void check_file(const char *filename) { + int j, hdr_checksum, hdr_type, hdr_unused, ok = 1; + FILE *f; + int got, size, hdr_size; + + f = open_input(filename); + got = fread(buf, 1, 16, f); + if(got < 0) { + perror(filename); + exit(1); + } else if(got < 16) { + fprintf(stderr, "%s is only %d bytes long, not a valid CART\n", + filename, (int)got); + exit(1); + } + + if(!has_cart_header(buf)) { + fprintf(stderr, "%s missing CART header\n", filename); + exit(1); + } + + printf("%s has CART header\n", filename); + + hdr_type = extract4(buf + 4); + hdr_checksum = extract4(buf + 8); + hdr_unused = extract4(buf + 12); + + if(hdr_type < 1 || hdr_type > CARTRIDGE_LAST_SUPPORTED) { + fprintf(stderr, "%s has invalid cart type %d (should be 1-%d)\n", + filename, hdr_type, CARTRIDGE_LAST_SUPPORTED); + exit(1); + } + + printf("%s is type %d: %s (%d bytes)\n", + filename, hdr_type, cart_types[hdr_type].name, cart_types[hdr_type].size); + + if(hdr_unused) { + fprintf(stderr, "warning: %s unused area in CART header is non-zero\n", filename); + } + + hdr_size = CARTRIDGE_kb[hdr_type] * 1024; + + while( (got = fread(buf, 1, CARTBUFLEN, f)) > 0) { + if(got < CARTBUFLEN) { + fprintf(stderr, "warning: %s data size not a multiple of %d bytes\n", + filename, CARTBUFLEN); + } + for(j = 0; j < got; j++) checksum += buf[j]; + size += got; + } + if(ferror(f)) { + perror(filename); + exit(1); + } + fclose(f); + + if(size != hdr_size) { + ok = 0; + fprintf(stderr, + "%s header says the data size should be %d bytes, but we read %d, ", + filename, hdr_size, size); + } + + if(size > hdr_size) { + fprintf(stderr, "junk at the end? downloaded in ASCII mode?\n"); + } else if(size < hdr_size) { + fprintf(stderr, "truncated?\n"); + } else { + printf("%s has correct data size, %d bytes\n", filename, size); + } + + if(hdr_checksum == checksum) { + printf("%s has valid checksum\n", filename); + } else { + ok = 0; + fprintf(stderr, "%s has BAD checksum\n", filename); + } + + printf("%s results: %s\n", filename, (ok ? "OK" : "FAILED")); + exit(!ok); +} + +void cleanup() { + if(outfile && !keep_outfile) unlink(outfile); /* ignore error here */ +} + +int main(int argc, char **argv) { + init(); + atexit(cleanup); + + if(argc < 2) { + usage(); + exit(0); + } + + while(++argv, --argc > 0) { + if(argv[0][0] == '-') { + switch(argv[0][1]) { + case 'l': + list_types(); + exit(0); + break; + + case 't': + type = atoi(&argv[0][2]); + if(type < 1 || type > CARTRIDGE_LAST_SUPPORTED) { + fprintf(stderr, "Invalid -t, use -t1 thru -t%d (not -t 1)\n\n", + CARTRIDGE_LAST_SUPPORTED); + usage(); + exit(1); + } + break; + + case 'o': + if(argv[0][2]) { + outfile = &argv[0][2]; + } else { + fprintf(stderr, "Invalid -o, use -ofilename (not -o filename)\n\n"); + exit(1); + } + break; + + case 'x': + if(argv[0][2]) { + outfile = &argv[0][2]; + extracting = 1; + } else { + fprintf(stderr, "Invalid -x, use -xfilename (not -x filename)\n\n"); + exit(1); + } + break; + + case 'c': + if(argv[0][2]) { + check_file(&argv[0][2]); /* exits */ + } else { + fprintf(stderr, "Invalid -c, use -cfilename (not -c filename)\n\n"); + exit(1); + } + break; + + case 'h': + case '?': + usage(); + exit(0); + break; + + default: + fprintf(stderr, "Invalid option %s\n\n", *argv); + usage(); + exit(1); + break; + } + } else { /* argv[0][0] != '-' */ + add_file(*argv); + } + } + + open_output(); + write_header(); + write_data(); + exit(0); +} diff --git a/mkcart.rst b/mkcart.rst new file mode 100644 index 0000000..dbf5480 --- /dev/null +++ b/mkcart.rst @@ -0,0 +1,111 @@ +====== +mkcart +====== + +------------------------------------------------------- +Convert between raw ROM images and atari800 CART format +------------------------------------------------------- + +.. |date| date:: + +:Manual section: 1 +:Manual group: DASM-Dillon +:Authors: `B. Watson <yalhcru@gmail.com>` +:Date: |date| +:Version: 2.10.12 +:Copyright: This document is licensed under the same terms as DASM + itself, see the file COPYING for details. + +SYNOPSIS +======== + +mkcart -oCARTFILE -tTYPE RAWFILE [RAWFILE ...] + +mkcart -cCARTFILE + +mkcart -xRAWFILE CARTFILE + +mkcart -l + +DESCRIPTION +=========== + +A companion tool to `dasm(1)`, mkcart can: + +- convert one or more `dasm(1)` raw (-f3) object files to a CART image + format for use with emulators such as `atari800(1)`. + +- convert a CART image back to a raw image. + +- check the integrity and report information about a CART image. + +OPTIONS +======= + +-tTYPE Cartridge type (1-67, see `-l` for list), default = guess (poorly!). + Only used in -o mode. + +-oCARTFILE Create CARTFILE from RAWFILE(s). `-t` optional but highly + recommended. + +-cCARTFILE Check integrity of file (header, checksum, and size). + +-xRAWFILE Create raw binary from CARTFILE (remove header). + +-l List all supported -t types with their sizes in + bytes and their human-readable names, and exit. + +-?, -h Show built-in help message. + +EXAMPLES +======== + +| # a standard 8KB cartridge: +| dasm example.asm -f3 -oexample.bin +| mkcart -oexample.cart -t1 example.bin + +| # a bankswitched OSS 16KB cartridge: +| dasm bank1.asm -f3 -obank1.bin +| dasm bank2.asm -f3 -obank2.bin +| mkcart -oexample.cart -t15 bank1.bin bank2.bin + +EXIT CODES +========== + +With -o and -x, mkcart will exit with status `0` if it was able +to complete the conversion, or `1` if something went wrong. + +With -c, mkcart will exit with status `0` if the image is OK (has a +valid header, known type, good checksum, etc), or `1` if not. + +BUGS +==== + +With -o and -x, the input files are opened, read, and closed twice: +once to calculate the checksum and verify that there's enough data, then +the header is written and the input files are re-opened and reread. Bad +Things will probably happen if any of the input files change in between +the two passes. + +The -x option should split bankswitched cartridges into multiple raw +images instead of one combined image. Workaround: use `split(1)` +or `dd(1)` to split the raw image into bank-sized chunks: + +| mkcart -xmac65.bin mac65.cart + +| # split into mac65.bank00 and mac65.bank01 +| split -b8192 mac65.bin mac65.bank + +| # same thing, with dd +| dd if=mac65.bin of=mac65.bank00 bs=1024 count=8 +| dd if=mac65.bin of=mac65.bank01 bs=1024 count=8 skip=8 + +Either way, you have to know the bank size (usually 8 or 16 KB), which +is less than ideal. + +SEE ALSO +======== + +* `dasm(1)` +* `atari800(1)` +* `cart.txt` @@ -7,14 +7,14 @@ ; location sound code will look at to see whether sound ; is disabled (0 = enabled, !0 = disabled). If you ; change this here, change it in sounds.h also! -sound_disabled = $06ff +sound_disabled = $03c0 ; since we're changing the font and colors, we'll save the old ; ones here. If you change these, change them in taipan.c ; also. -fontsave = $06fc -color1save = $06fd -color2save = $06fe +fontsave = $03c1 +color1save = $03c2 +color2save = $003c3 ; where our screen was loaded (see newtitle.pl) ;screendata = $2400 @@ -5,5 +5,6 @@ ; in atari800. H: needs to be set writable and pointed to the current ; directory. + .rodata _port_stat_screen: .incbin "PORTSTAT.DAT" @@ -34,7 +34,7 @@ repeats = tmp4 ; this must agree with newtitle.s ; 0 = enabled, 1 = disabled -sound_disabled = $06ff +sound_disabled = $03c0 _under_attack_sound: ; C version was: @@ -122,6 +122,7 @@ extern void __fastcall__ clear_lorcha(int which); /* used to set the background/text colors here, but now the title screen does it (newtitle.s) */ void atari_text_setup() { +#ifndef CART_TARGET jsleep(1); POKE(560, PEEK(212)); // restore the POKE(561, PEEK(213)); // display list @@ -129,6 +130,7 @@ void atari_text_setup() { POKE(559, 34); // turn on the screen (normal playfield) jsleep(1); POKE(756, FONT_ADDR / 256); // use our custom font +#endif POKE(731, 1); // disable keyclick on XL/XE (does nothing on 400/800) } @@ -252,9 +254,14 @@ void cprint_li_yuen(void); unsigned char firmpos; -/* use page 6 for these buffers */ +/* use page 6 for these buffers, for .xex build. Otherwise they'e BSS. */ +#ifdef CART_TARGET +char firm[23]; +char num_buf[20]; +#else char *firm = (char *) 0x680; char *num_buf = (char *) 0x600; +#endif char *item[] = { "Opium", "Silk", "Arms", "General Cargo" }; @@ -1939,12 +1946,12 @@ void final_stats(void) /* restore CHBAS to its original value, generally the ROM font. This is called fontsave in newtitle.s. */ - POKE(756, PEEK(0x6fc)); + POKE(756, PEEK(0x3c1)); /* restore COLOR1 and COLOR2. These locations are called color1save and color2save in newtitle.s. */ - POKE(709, PEEK(0x6fd)); - POKE(710, PEEK(0x6fe)); + POKE(709, PEEK(0x3c2)); + POKE(710, PEEK(0x3c3)); exit(0); } @@ -2392,7 +2399,7 @@ void li_yuen_extortion(void) { compradores_report(); cprint_li_yuen(); - cputs("asks "); + cputs(" asks "); cprintfancy(amount); cputs(" in donation\r\nto the temple of Tin Hau, the Sea\r\nGoddess. Will you pay? "); choice = yngetc(0); @@ -3207,7 +3214,7 @@ int main(void) { if((port != 1) && (li == 0) && (!one_chance_in(4))) { compradores_report(); cprint_li_yuen(); - cputs("has sent a Lieutenant,\r\n" + cputs(" has sent a Lieutenant,\r\n" "Taipan. He says his admiral wishes\r\n" "to see you in Hong Kong, posthaste!\r\n"); bad_joss_sound(); |