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 00000000..350180c9 Binary files /dev/null and b/presets/apple2/parrot-apple2.hires.lz4 differ 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'],