From c189875be30448ecd91c3c32758ec325da87a86c Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 21 Nov 2023 15:06:13 -0600 Subject: [PATCH] apple2: AppleSingle header for CC65 by default, try to parse 4-byte DOS headers --- presets/apple2/lz4test.c | 55 +++++++++++++++++ presets/apple2/parrot-apple2.hires.lz4 | Bin 0 -> 5028 bytes src/common/devices.ts | 2 + src/machine/apple2.ts | 78 +++++++++++++++++-------- src/platform/apple2.ts | 2 +- src/worker/workermain.ts | 2 +- 6 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 presets/apple2/lz4test.c create mode 100644 presets/apple2/parrot-apple2.hires.lz4 diff --git a/presets/apple2/lz4test.c b/presets/apple2/lz4test.c new file mode 100644 index 00000000..028e3fa1 --- /dev/null +++ b/presets/apple2/lz4test.c @@ -0,0 +1,55 @@ + +/* +Test of the LZ4 decompression library +with a hires graphics image. +*/ + +// CC65 config, reserves space for the HGR1 screen buffer +#define CFGFILE apple2-hgr.cfg + +#pragma data-name(push,"HGR") +// this segment is required, but we leave it empty +// since we're going to decompress the image here +#pragma data-name(pop) + +#include +#include +#include +#include +#include +#include +#include +#include + +// STROBE = write any value to an I/O address +#define STROBE(addr) __asm__ ("sta %w", addr) + +// start address of the two hi-res graphics regions +#define HGR1 0x2000 +#define HGR2 0x4000 + +// the LZ4 compressed data +const unsigned char BITMAP_DATA_LZ4[] = { + #embed "parrot-apple2.hires.lz4" +}; + + +// clear screen and set graphics mode +void clear_hgr1() { + memset((char*)HGR1, 0, 0x2000); // clear page 1 + STROBE(0xc052); // turn off mixed-mode + STROBE(0xc054); // page 1 + STROBE(0xc057); // hi-res + STROBE(0xc050); // set graphics mode +} + +int main (void) +{ + // set hgr1 mode and clear + clear_hgr1(); + // skip the header (usually 11 bytes) + decompress_lz4(BITMAP_DATA_LZ4+11, (char*)HGR1, 0x2000); + // wait for a key + cgetc(); + return EXIT_SUCCESS; +} diff --git a/presets/apple2/parrot-apple2.hires.lz4 b/presets/apple2/parrot-apple2.hires.lz4 new file mode 100644 index 0000000000000000000000000000000000000000..350180c964c8fc2da4baebf1d7be6372208777da GIT binary patch literal 5028 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 literal 0 HcmV?d00001 diff --git a/src/common/devices.ts b/src/common/devices.ts index f0cac962..0d3933d7 100644 --- a/src/common/devices.ts +++ b/src/common/devices.ts @@ -243,6 +243,8 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P } loadROM(data: Uint8Array, title?: string): void { if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize); + if (data.length > this.rom.length) + throw new Error(`ROM too big: ${data.length} > ${this.rom.length}}`); this.rom.set(data); } loadState(state) { diff --git a/src/machine/apple2.ts b/src/machine/apple2.ts index 643c0bb6..a8f9c701 100644 --- a/src/machine/apple2.ts +++ b/src/machine/apple2.ts @@ -2,13 +2,7 @@ import { MOS6502, MOS6502State } from "../common/cpu/MOS6502"; import { Bus, BasicScanlineMachine, SavesState, AcceptsBIOS } from "../common/devices"; import { KeyFlags } from "../common/emu"; // TODO -import { hex, lzgmini, stringToByteArray, RGBA, printFlags } from "../common/util"; - -// TODO: read prodos/ca65 header? -const VM_BASE = 0x803; // where to JMP after pr#6 -const LOAD_BASE = VM_BASE; -const PGM_BASE = VM_BASE; -const HDR_SIZE = PGM_BASE - LOAD_BASE; +import { hex, lzgmini, stringToByteArray, RGBA, printFlags, arrayCompare } from "../common/util"; interface AppleIIStateBase { ram : Uint8Array; @@ -43,7 +37,11 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { canvasWidth = 280; numVisibleScanlines = 192; numTotalScanlines = 262; - defaultROMSize = 0xbf00-0x803; // TODO + defaultROMSize = 0x13000; // we'll never need one that big, but... + + // these are set later + LOAD_BASE = 0; + HDR_SIZE = 0; ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM bios : Uint8Array; @@ -78,8 +76,8 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { // SHOULD load program into RAM here, but have to do it // below instead. return 0; - case 1: return VM_BASE&0xff; - case 2: return (VM_BASE>>8)&0xff; + case 1: return this.LOAD_BASE&0xff; + case 2: return (this.LOAD_BASE>>8)&0xff; default: return 0; } } @@ -97,8 +95,7 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { // into RAM and returning the JMP here, instead of above // where it would otherwise belong. if (this.rom) { - console.log(`Loading program into Apple ][ RAM at \$${PGM_BASE.toString(16)}`); - this.ram.set(this.rom.slice(HDR_SIZE), PGM_BASE); + this.loadRAMWithProgram(); } return 0x4c; // JMP case 1: return 0x20; @@ -168,26 +165,57 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS { console.log("will load BIOS to end of memory anyway..."); } this.bios = Uint8Array.from(data); - this.ram.set(this.bios, 0x10000 - this.bios.length); - this.ram[0xbf00] = 0x4c; // fake DOS detect for C - this.ram[0xbf6f] = 0x01; // fake DOS detect for C } - loadROM(data) { - if (data.length == 35*16*256) { // is it a disk image? - var diskii = new DiskII(this, data); - this.slots[6] = diskii; - } else { // it's a binary, use a fake drive - super.loadROM(data); - this.slots[6] = this.fakeDrive; + loadROM(data) { + // is it a 16-sector 35-track disk image? + if (data.length == 16 * 35 * 256) { + var diskii = new DiskII(this, data); + this.slots[6] = diskii; + this.reset(); + } else { // it's a binary, use a fake drive + // set this.rom variable + super.loadROM(data); + // AppleSingle header? https://github.com/cc65/cc65/blob/master/libsrc/apple2/exehdr.s + if (arrayCompare(this.rom.slice(0, 4), [0x00, 0x05, 0x16, 0x00])) { + this.LOAD_BASE = this.rom[0x39] | (this.rom[0x38] << 8); // big endian + this.HDR_SIZE = 58; + } else { + // 4-byte DOS header? (TODO: hacky detection) + const origin = this.rom[0] | (this.rom[1] << 8); + const size = this.rom[2] | (this.rom[3] << 8); + let isPlausible = origin < 0xc000 + && origin + size < 0x13000 + && (origin == 0x803 || (origin & 0xff) == 0); + if (size == data.length - 4 && isPlausible) { + this.LOAD_BASE = origin; + this.HDR_SIZE = 4; + } else { + // default = raw binary @ $803 + this.LOAD_BASE = 0x803; + this.HDR_SIZE = 0; + } + } + this.slots[6] = this.fakeDrive; + } + } + loadRAMWithProgram() { + console.log(`Loading program into Apple ][ RAM at \$${this.LOAD_BASE.toString(16)}`); + // truncate if needed to fit into RAM + const exedata = this.rom.slice(this.HDR_SIZE, this.HDR_SIZE + this.ram.length - this.LOAD_BASE); + this.ram.set(exedata, this.LOAD_BASE); + // fake DOS detect for CC65 (TODO?) + if (this.HDR_SIZE == 58) { + this.ram[0xbf00] = 0x4c; + this.ram[0xbf6f] = 0x01; + } } - } reset() { - super.reset(); this.auxRAMselected = false; this.auxRAMbank = 1; this.writeinhibit = true; this.ram.fill(0, 0x300, 0x400); // Clear soft-reset vector // (force hard reset) + super.reset(); this.skipboot(); } skipboot() { @@ -479,7 +507,7 @@ var Apple2Display = function(pixels : Uint32Array, apple : AppleGRParams) { var oldgrmode = -1; var textbuf = new Array(40*24); - const flashInterval = 500; + const flashInterval = 250; // https://mrob.com/pub/xapple2/colors.html const loresColor = [ diff --git a/src/platform/apple2.ts b/src/platform/apple2.ts index cb11772e..71368276 100644 --- a/src/platform/apple2.ts +++ b/src/platform/apple2.ts @@ -16,7 +16,7 @@ const APPLE2_PRESETS : Preset[] = [ {id:'cosmic.c', name:'Cosmic Impalas'}, {id:'farmhouse.c', name:"Farmhouse Adventure"}, {id:'yum.c', name:"Yum Dice Game"}, - {id:'lzgtest.c', name:"LZG Decompressor"}, + {id:'lz4test.c', name:"LZ4 Decompressor"}, {id:'hgrtest.a', name:"HGR Test", category:"Assembly Language"}, {id:'conway.a', name:"Conway's Game of Life"}, {id:'lz4fh.a', name:"LZ4FH Decompressor"}, diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts index 54835fe1..9615e4eb 100644 --- a/src/worker/workermain.ts +++ b/src/worker/workermain.ts @@ -249,7 +249,7 @@ var PLATFORM_PARAMS = { arch: '6502', define: ['__APPLE2__'], cfgfile: 'apple2.cfg', - libargs: [ '--lib-path', '/share/target/apple2/drv', '-D', '__EXEHDR__=0', 'apple2.lib'], + libargs: [ '--lib-path', '/share/target/apple2/drv', 'apple2.lib'], __CODE_RUN__: 16384, code_start: 0x803, acmeargs: ['-f', 'apple'],