From 5a5eec982c275a3d07b17314024906a56ba3707f Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Sat, 11 Jul 2020 09:51:26 -0500 Subject: [PATCH] getROMExtension() for some platforms, changed d/l filenames --- src/common/baseplatform.ts | 3 ++- src/ide/ui.ts | 11 +++++++---- src/machine/sms.ts | 4 ++-- src/platform/apple2.ts | 4 ++++ src/platform/atari7800.ts | 1 + src/platform/c64.ts | 7 +++++++ src/platform/nes.ts | 4 +++- src/platform/vcs.ts | 4 +++- src/platform/x86.ts | 6 +++++- src/platform/zmachine.ts | 4 ++++ test/cli/testplatforms.js | 11 +++++++++++ .../sms-sms-libcv/climber.c-sms-sms-libcv.rom | Bin 0 -> 49152 bytes 12 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 test/roms/sms-sms-libcv/climber.c-sms-sms-libcv.rom diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index 5248f69b..39200ab3 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -2,7 +2,7 @@ import { RAM, RasterVideo, KeyFlags, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller } from "./emu"; import { hex, printFlags, invertMap, getBasePlatform } from "./util"; import { CodeAnalyzer } from "./analysis"; -import { Segment } from "./workertypes"; +import { Segment, FileData } from "./workertypes"; import { disassemble6502 } from "./cpu/disasm6502"; import { disassembleZ80 } from "./cpu/disasmz80"; import { Z80 } from "./cpu/ZilogZ80"; @@ -80,6 +80,7 @@ export interface Platform { resume() : void; loadROM(title:string, rom:any); // TODO: Uint8Array loadBIOS?(title:string, rom:Uint8Array); + getROMExtension?(rom:FileData) : string; loadState?(state : EmuState) : void; saveState?() : EmuState; diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 26628007..3923dc66 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -880,12 +880,15 @@ function _downloadROMImage(e) { alertError("Please finish compiling with no errors before downloading ROM."); return true; } + var prefix = getFilenamePrefix(getCurrentMainFilename()); if (current_output instanceof Uint8Array) { var blob = new Blob([current_output], {type: "application/octet-stream"}); - saveAs(blob, getCurrentMainFilename()+".rom"); + var suffix = (platform.getROMExtension && platform.getROMExtension(current_output)) + || "-" + getBasePlatform(platform_id) + ".bin"; + saveAs(blob, prefix + suffix); } else { var blob = new Blob([(current_output).code], {type: "text/plain"}); - saveAs(blob, getCurrentMainFilename()+".js"); + saveAs(blob, prefix + ".js"); } } @@ -905,7 +908,7 @@ function _downloadProjectZipFile(e) { } }); zip.generateAsync({type:"blob"}).then( (content) => { - saveAs(content, getCurrentMainFilename() + ".zip"); + saveAs(content, getCurrentMainFilename() + "-" + getBasePlatform(platform_id) + ".zip"); }); }); } @@ -923,7 +926,7 @@ function _downloadAllFilesZipFile(e) { })).then(() => { return zip.generateAsync({type:"blob"}); }).then( (content) => { - return saveAs(content, platform_id + "-all.zip"); + return saveAs(content, getBasePlatform(platform_id) + "-all.zip"); }); }); }); diff --git a/src/machine/sms.ts b/src/machine/sms.ts index 4b21297c..9b119816 100644 --- a/src/machine/sms.ts +++ b/src/machine/sms.ts @@ -50,7 +50,7 @@ export class SG1000 extends BaseZ80VDPBasedMachine { read = newAddressDecoder([ [0xc000, 0xffff, 0x3ff, (a) => { return this.ram[a]; }], - [0x0000, 0xbfff, 0xffff, (a) => { return this.rom[a]; }], + [0x0000, 0xbfff, 0xffff, (a) => { return this.rom && this.rom[a]; }], ]); write = newAddressDecoder([ [0xc000, 0xffff, 0x3ff, (a,v) => { this.ram[a] = v; }], @@ -141,7 +141,7 @@ export class SMS extends SG1000 { getPagedROM(a:number, reg:number) { //if (!(a&0xff)) console.log(hex(a), reg, this.pagingRegisters[reg], this.romPageMask); - return this.rom[a + ((this.pagingRegisters[reg] & this.romPageMask) << 14)]; // * $4000 + return this.rom && this.rom[a + ((this.pagingRegisters[reg] & this.romPageMask) << 14)]; // * $4000 } read = newAddressDecoder([ diff --git a/src/platform/apple2.ts b/src/platform/apple2.ts index 2474baf5..f80fc38f 100644 --- a/src/platform/apple2.ts +++ b/src/platform/apple2.ts @@ -76,6 +76,10 @@ class NewApple2Platform extends Base6502MachinePlatform implements Plat {name:'I/O',start:0xc000,size:0x1000,type:'io'}, {name:'ROM',start:0xd000,size:0x3000-6,type:'rom'}, ] } }; + getROMExtension(rom:Uint8Array) { + if (rom && rom.length == 35*16*256) return ".dsk"; // DSK image + return ".bin"; + }; } PLATFORMS['apple2.mame'] = Apple2MAMEPlatform; diff --git a/src/platform/atari7800.ts b/src/platform/atari7800.ts index 56f20fcf..5764fe3a 100644 --- a/src/platform/atari7800.ts +++ b/src/platform/atari7800.ts @@ -26,6 +26,7 @@ class Atari7800Platform extends Base6502MachinePlatform implements Pl {name:'RAM',start:0x1800,size:0x1000,type:'ram'}, // TODO: shadow ram {name:'Cartridge ROM',start:0x4000,size:0xc000,type:'rom'}, ] } }; + getROMExtension() { return ".a78"; } } /// diff --git a/src/platform/c64.ts b/src/platform/c64.ts index 60d2f808..df894651 100644 --- a/src/platform/c64.ts +++ b/src/platform/c64.ts @@ -65,6 +65,13 @@ class C64WASMPlatform extends Base6502MachinePlatform implement showHelp() { window.open("https://sta.c64.org/cbm64mem.html", "_help"); } + getROMExtension(rom:Uint8Array) { + /* + if (rom && rom[0] == 0x00 && rom[1] == 0x80 && rom[2+4] == 0xc3 && rom[2+5] == 0xc2) return ".crt"; + */ + if (rom && rom[0] == 0x01 && rom[1] == 0x08) return ".prg"; + else return ".bin"; + } } PLATFORMS['c64'] = C64WASMPlatform; diff --git a/src/platform/nes.ts b/src/platform/nes.ts index 738b9a8b..024e1b15 100644 --- a/src/platform/nes.ts +++ b/src/platform/nes.ts @@ -264,7 +264,9 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable { getOriginPC() { // TODO: is actually NMI return (this.readAddress(0xfffa) | (this.readAddress(0xfffb) << 8)) & 0xffff; } - getDefaultExtension() { return ".c"; }; + getDefaultExtension() { return ".c"; } + + getROMExtension() { return ".nes"; } reset() { //this.nes.cpu.reset(); // doesn't work right, crashes diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts index 162da99f..f6b98c6d 100644 --- a/src/platform/vcs.ts +++ b/src/platform/vcs.ts @@ -248,7 +248,9 @@ class VCSPlatform extends BasePlatform { if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic"; return "dasm"; } - getDefaultExtension() { return ".a"; }; + getDefaultExtension() { return ".a"; } + + getROMExtension() { return ".a26"; } getDebugCategories() { return ['CPU','Stack','PIA','TIA']; diff --git a/src/platform/x86.ts b/src/platform/x86.ts index f2b09304..2fae9a83 100644 --- a/src/platform/x86.ts +++ b/src/platform/x86.ts @@ -159,6 +159,10 @@ class X86PCPlatform implements Platform { {name:'BIOS Expansions',start:0xc8000,size:0x28000,type:'rom'}, {name:'PC BIOS',start:0xf0000,size:0x10000,type:'rom'}, ] } }; - } + + getROMExtension(rom : Uint8Array) { + return ".exe"; + } +} PLATFORMS['x86'] = X86PCPlatform; diff --git a/src/platform/zmachine.ts b/src/platform/zmachine.ts index 98e1f848..973ef34d 100644 --- a/src/platform/zmachine.ts +++ b/src/platform/zmachine.ts @@ -809,6 +809,10 @@ class ZmachinePlatform implements Platform { this.reset(); } + getROMExtension() { + return ".z" + (this.zvm.version || 5); + } + reset(): void { if (this.zfile == null) return; this.zvm = new ZVM(); diff --git a/test/cli/testplatforms.js b/test/cli/testplatforms.js index 43829529..faee75e9 100644 --- a/test/cli/testplatforms.js +++ b/test/cli/testplatforms.js @@ -178,6 +178,7 @@ async function testPlatform(platid, romname, maxframes, callback) { assert.ok(dinfo && dinfo.length > 0, dcat + " empty"); assert.ok(dinfo.length < 80*24, dcat + " too long"); assert.ok(dinfo.indexOf('undefined') < 0, dcat + " undefined"); + assert.ok(dinfo.indexOf('Display On: false') < 0, dcat + " display off"); } if (lastrastervideo) { var png = new PNG({width:lastrastervideo.width, height:lastrastervideo.height}); @@ -190,6 +191,9 @@ async function testPlatform(platid, romname, maxframes, callback) { fs.writeFileSync("./test/output/"+platid+"-"+romname+".png", pngbuffer); } catch (e) { console.log(e) } } + // misc + assert.ok(platform.getDefaultExtension().startsWith('.')); + if (platform.getROMExtension) assert.ok(platform.getROMExtension().startsWith(".")); return platform; } @@ -308,6 +312,13 @@ describe('Platform Replay', () => { } }); }); + it('Should run sms-sms-libcv', async () => { + var platform = await testPlatform('sms-sms-libcv', 'climber.c-sms-sms-libcv.rom', 200, (platform, frameno) => { + if (frameno == 122) { + keycallback(Keys.RIGHT.c, Keys.VK_RIGHT.c, 1); + } + }); + }); it('Should run atari7800', async () => { var platform = await testPlatform('atari7800', 'sprites.dasm.rom', 92, (platform, frameno) => { if (frameno == 62) { diff --git a/test/roms/sms-sms-libcv/climber.c-sms-sms-libcv.rom b/test/roms/sms-sms-libcv/climber.c-sms-sms-libcv.rom new file mode 100644 index 0000000000000000000000000000000000000000..92ff17054d905bcbb0142d327c280045afdbb66a GIT binary patch literal 49152 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 literal 0 HcmV?d00001