├── .eslintignore ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGES.md ├── CLI-EMU.md ├── Gruntfile.js ├── LICENSE ├── MZ-700 ├── client.html ├── mz700-emu-ws.ts ├── mz700-emu.css ├── mz700-emu.ts ├── mz700-key-matrix.ts ├── mz700-memory.ts ├── mz700-new-monitor.ts ├── mz700-web-ui.ts ├── mz700-worker.ts ├── mz700.ts └── pcg700-sample.asm ├── README.md ├── Z80 ├── Z80-line-assembler.ts ├── Z80.ts ├── assembler.ts ├── bin-util.ts ├── imem.ts ├── memory-bank.ts ├── memory-block-cbrw.ts ├── memory-block-cbw.ts ├── memory-block.ts └── register.ts ├── bin ├── bin2mzt.js ├── cli-command │ ├── breakpoint.js │ ├── cmt.js │ ├── command.js │ ├── conf.js │ ├── exit.js │ ├── jump.js │ ├── mem.js │ ├── mzt-read-file.js │ ├── register.js │ ├── run.js │ ├── sendkey.js │ ├── step.js │ ├── stop.js │ └── vram.js ├── lib │ ├── get-package-json.js │ ├── mz-files.js │ └── start-web-server.js ├── mz700-cli.js ├── mz700-web.js ├── mz700-ws.js ├── mzasm.js └── mzdasm.js ├── emu-ws.html ├── emu.html ├── image ├── btnFullscreen-off.png ├── btnFullscreen-on.png ├── btnKeyboard-off.png ├── btnKeyboard-on.png ├── btnReset-off.png ├── btnReset-on.png ├── btnRun-off.png ├── btnRun-on.png ├── btnStepIn-disabled.png ├── btnStepIn-off.png ├── btnStepIn-on.png ├── btnStop-off.png ├── btnStop-on.png ├── buttons.svg ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-48x48.png ├── favicon-96x96.png ├── favicon.png ├── gbtnReset-on.png ├── icon-sound-off.svg ├── icon-sound-on.svg ├── icon-sound.svg └── title.png ├── js └── .gitignore ├── lib ├── PCG-700.ts ├── change-ext.ts ├── do-later.ts ├── event-dispatcher.ts ├── flip-flop-counter.ts ├── ic556.ts ├── intel-8253.ts ├── jquery-plugin │ ├── jquery.Z80-addr-spec.js │ ├── jquery.Z80-mem.css │ ├── jquery.Z80-mem.js │ ├── jquery.Z80-reg.css │ ├── jquery.Z80-reg.js │ ├── jquery.asmeditor.js │ ├── jquery.asmlist.css │ ├── jquery.asmlist.js │ ├── jquery.asmview.js │ ├── jquery.emu-speed-control.css │ ├── jquery.emu-speed-control.js │ ├── jquery.mz-data-recorder.js │ ├── jquery.mz-sound-control.js │ ├── jquery.mz700-img-button.js │ ├── jquery.mz700-kb.css │ ├── jquery.mz700-kb.js │ ├── jquery.mz700-scrn.js │ ├── jquery.soundctrl.css │ ├── jquery.soundctrl.js │ ├── jquery.tabview.css │ ├── jquery.tabview.js │ ├── jquery.toggle-button.js │ ├── jquery.tool-window.css │ ├── jquery.tool-window.js │ ├── jquery.vscrlist.js │ └── jquery_plugin_class.js ├── jsonp.ts ├── mz-beep.ts ├── mz-data-recorder.ts ├── mz-mmio.ts ├── mz-tape-header.ts ├── mz-tape.ts ├── mz700-canvas-renderer.ts ├── mz700-cg.ts ├── mz700-charcode.ts ├── mz700-scrn.ts ├── number-util.ts ├── oct.ts ├── parse-addr.ts ├── parse-request.ts └── user-agent-util.ts ├── misc ├── MZ700FON.php ├── MZ700FON2json.php └── bin2json.php ├── mz_newmon ├── ROMS │ ├── NEWMON.ROM │ └── NEWMON7.ROM ├── newmon_command.txt └── newmon_readme.txt ├── package-lock.json ├── package.json ├── sample └── PCG-700-SAMPLE.asm ├── test ├── .gitignore ├── ASM │ ├── .gitignore │ ├── BdHopper._asm_ │ ├── NEWMON._asm_ │ ├── NEWMON7._asm_ │ └── WH_newmon._asm_ ├── CPIR.asm ├── CPIR.mzt ├── Z80-asm-dasm.test.ts ├── Z80-assembler.test.ts ├── Z80-dasm.test.ts ├── Z80-line-assembler.test.ts ├── Z80-ope-bit.test.ts ├── Z80-ope-calc16.test.ts ├── Z80-ope-calc8.test.ts ├── Z80-ope-load16.test.ts ├── Z80-ope-load8.test.ts ├── Z80-ope-res.test.ts ├── Z80-ope-rotate.test.ts ├── Z80-ope-search.test.ts ├── Z80-ope-trans.test.ts ├── Z80-register.test.ts ├── Z80_imp_stat.ts ├── Z80_imp_stat.txt ├── change-ext.test.ts ├── lib │ ├── UnitTest.js │ └── Z80Tester.js ├── mz-tape.test.ts ├── mz700-memory.test.ts ├── number-util.test.ts ├── test.sh └── testdata │ ├── .gitignore │ ├── BdHopper.mzt │ ├── NEWMON.rom │ ├── NEWMON7.rom │ └── WH_newmon.mzt └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | /js 2 | /node_modules 3 | /sample 4 | /test 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tags 3 | .tscache/ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | - "10" 5 | -------------------------------------------------------------------------------- /CLI-EMU.md: -------------------------------------------------------------------------------- 1 | `mz700-cli` - RUN THE CLI EMULATOR with Node.js 2 | =============================================== 3 | 4 | ### COMMAND LINE 5 | 6 | ``` 7 | > mz700-cli [-c ] [] 8 | ``` 9 | 10 | ### OPTIONS 11 | 12 | * c - Set a MZT file to the data recorder as CMT. 13 | 14 | ### PARAMETERS 15 | 16 | * `` - A MZT-filename to be loaded to the memory immedietely. 17 | 18 | ### COMMANDS 19 | 20 | * __`exit`__ - Exit from the emulator. 21 | * __`run`__ - Run MZ-700 emulation. 22 | * __`stop`__ - Stop emulation. 23 | * __`step`__ _`[]`_ - Execute N instructions and stop. num default is 1. 24 | * __`vram`__ - Print VRAM to console. 25 | * __`reg`__ - Print register. 26 | * __`key`__ _``_ - Convert the string to MZ-700's Key-Matrix, Then push those stroke. 27 | * __`jp`__ _``_ - Set the PC of Z80 CPU. 28 | * __`mem set`__ _` [ ...]`_ - Write data to the memory 29 | * __`mem dump`__ _``_ - Print the contents of the memory. 30 | * __`cmt set`__ _``_ - Set CMT to the data recorder. 31 | * __`cmt eject`__ - Eject CMT. 32 | * __`cmt play`__ - Push the PLAY button of the data recorder. 33 | * __`cmt rec`__ - Push the REC button of the data recorder. 34 | * __`cmt stop`__ - Push the STOP button of the data recorder. 35 | * __`bp set`__ _``_ - Set break points at the address. 36 | * __`bp rm`__ _``_ - Remove the break points set at the address. 37 | * __`bp clear`__ - Clear all break points. 38 | * __`conf key duration make `__ - Set key making duration by millisec. 39 | * __`conf key duration release `__ - Set key releasing duration by millisec. 40 | * __`conf key duration make`__ - Print key making duration. 41 | * __`conf key duration release`__ - Print key releasing duration. 42 | 43 | #### Parameters for the commands 44 | 45 | * _``_ : Number. 46 | * _``_, _``_ : Specify the address like `0123h` as hexadecimal or `1024` as decimal 47 | * _``_ : String structured by the keys of the MZ-700 Key-Matrix. 48 | 49 | 50 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | grunt.initConfig({ 4 | clean: [ 5 | "./js/*.css", 6 | "./js/*.js", 7 | "./js/*.map", 8 | "./js/**/*.css", 9 | "./js/**/*.js", 10 | "./js/**/*.map", 11 | ], 12 | copy: { 13 | default: { 14 | files: { 15 | "./js/": [ 16 | "./lib/jquery-plugin/*", 17 | "./MZ-700/mz700-emu.css", 18 | ], 19 | "./js/lib/codemirror.css": 20 | "./node_modules/codemirror/lib/codemirror.css", 21 | } 22 | } 23 | }, 24 | ts: { 25 | default: { 26 | files: [ 27 | { 28 | src: [ 29 | "./MZ-700/mz700-emu.ts", 30 | "./MZ-700/mz700-worker.ts", 31 | "./MZ-700/mz700-emu-ws.ts", 32 | ] 33 | }, 34 | ], 35 | "tsconfig": "./tsconfig.json", 36 | }, 37 | }, 38 | browserify: { 39 | release: { 40 | files: { 41 | "./js/mz700-emu-ws.js": [ 42 | "./js/MZ-700/mz700-emu-ws.js", 43 | ], 44 | "./js/mz700-emu.js": [ 45 | "./js/MZ-700/mz700-emu.js", 46 | ], 47 | "./js/mz700-worker.js": [ 48 | "./js/MZ-700/mz700-worker.js", 49 | ], 50 | }, 51 | }, 52 | debug: { 53 | files: { 54 | "./js/mz700-emu-ws.min.js": [ 55 | "./js/MZ-700/mz700-emu-ws.js", 56 | ], 57 | "./js/mz700-emu.min.js": [ 58 | "./js/MZ-700/mz700-emu.js", 59 | ], 60 | "./js/mz700-worker.min.js": [ 61 | "./js/MZ-700/mz700-worker.js", 62 | ], 63 | }, 64 | }, 65 | }, 66 | uglify: { 67 | default: { 68 | files: { 69 | "./js/mz700-emu-ws.min.js": [ 70 | "./js/mz700-emu-ws.js", 71 | ], 72 | "./js/mz700-emu.min.js": [ 73 | "./js/mz700-emu.js", 74 | ], 75 | "./js/mz700-worker.min.js": [ 76 | "./js/mz700-worker.js", 77 | ], 78 | }, 79 | options: { 80 | sourceMap: true, 81 | }, 82 | }, 83 | }, 84 | }); 85 | 86 | grunt.loadNpmTasks('grunt-contrib-clean'); 87 | grunt.loadNpmTasks("grunt-ts"); 88 | grunt.loadNpmTasks("grunt-browserify"); 89 | grunt.loadNpmTasks('grunt-contrib-uglify-es'); 90 | grunt.loadNpmTasks('grunt-contrib-copy'); 91 | 92 | grunt.registerTask("debug", [ 93 | "copy", 94 | "ts", 95 | "browserify:debug", 96 | ]); 97 | grunt.registerTask("release", [ 98 | "copy", 99 | "ts", 100 | "browserify:release", 101 | "uglify", 102 | ]); 103 | grunt.registerTask("default", [ 104 | "debug", 105 | ]); 106 | }; 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Koji Takami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MZ-700/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 33 | 34 | -------------------------------------------------------------------------------- /MZ-700/mz700-emu-ws.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* tslint:disable:no-console */ 3 | import TransWorker from "transworker"; 4 | import MZ700 from "./mz700"; 5 | import MZ700Scrn from "../lib/mz700-scrn"; 6 | import MZ700WebUI from "./mz700-web-ui"; 7 | 8 | async function main() { 9 | // Create MZ-700 Emulator 10 | const mz700js = await TransWorker.WebSocketClient.createInterface( 11 | "ws://localhost:5000", MZ700, 12 | { syncType: TransWorker.SyncTypePromise }); 13 | 14 | // MZ-700 Screen 15 | const mz700screen = document.querySelector("#mz700screen") as HTMLElement; 16 | const mz700scrn = new MZ700Scrn(mz700screen); 17 | const canvas = document.createElement("CANVAS") as HTMLCanvasElement; 18 | mz700screen.style.display = "none"; 19 | mz700screen.appendChild(canvas); 20 | mz700scrn.create({canvas}); 21 | canvas.style.height = "calc(100% - 1px)"; 22 | 23 | mz700scrn.setupRendering(); 24 | mz700js.subscribe("onUpdateScrn", canvasData => { 25 | const buffer = Buffer.from(canvasData, "base64"); 26 | const array = Uint8ClampedArray.from(buffer); 27 | const imageData = new ImageData(array, 320, 200); 28 | mz700scrn._ctx.putImageData(imageData, 0, 0); 29 | }); 30 | 31 | // Create user interface 32 | await MZ700WebUI.createUI(mz700js, mz700screen, canvas); 33 | } 34 | 35 | main().catch(err => console.warn(err.stack)); 36 | -------------------------------------------------------------------------------- /MZ-700/mz700-emu.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | height:25px; 3 | line-height:25px; 4 | margin:0; 5 | padding: 0; 6 | font-size:0pt; 7 | } 8 | .MZ-700 { 9 | padding:0px; 10 | overflow:hidden; 11 | width:100%; 12 | height:100%; 13 | } 14 | .MZ-700 button { 15 | background-color: blue; 16 | color: white; 17 | border: solid 2px blue; 18 | padding: 5px; 19 | margin: 5px 2px; 20 | font-size: 11pt; 21 | font-weight:bold; 22 | } 23 | .MZ-700 button:hover { 24 | background-color: white; 25 | color: blue; 26 | } 27 | .MZ-700 button:disabled { 28 | border-color: gray; 29 | background-color: gray; 30 | color: silver; 31 | } 32 | 33 | #mz700screen { 34 | border: solid 1px black; 35 | background-color: black; 36 | padding:0; 37 | z-index: -1; 38 | } 39 | 40 | #control-panel 41 | { 42 | overflow: auto; 43 | background-color: white; 44 | } 45 | #mz700screen { 46 | position:fixed; 47 | } 48 | 49 | .cmtPlayImage { 50 | display:inline-block; 51 | transform:rotate(-90deg); 52 | } 53 | 54 | @media screen and (orientation: portrait) { 55 | #mz700screen { 56 | position:relative; 57 | } 58 | #control-panel 59 | { 60 | position:relative; 61 | opacity:1; 62 | } 63 | } 64 | @media screen and (orientation: landscape) { 65 | #control-panel 66 | { 67 | opacity:0.8; 68 | } 69 | } 70 | .MZ-700 .emulation-panel { 71 | background-color: white; 72 | min-width:690px; 73 | } 74 | .MZ-700 .data-recorder .cmt-message { 75 | font-weight: bold; 76 | } 77 | 78 | .source-list .y-scroll-pane { 79 | height: 440px; 80 | } 81 | .source-list textarea { 82 | height:450px; 83 | box-sizing: border-box; 84 | width:calc(100% - 5px); 85 | margin:2px; 86 | padding:2px; 87 | border:none; 88 | resize:vertical; 89 | } 90 | 91 | #updateReg { 92 | width:80px; 93 | height:30px; 94 | } 95 | 96 | .imm-exec input.address { 97 | width:60px; 98 | } 99 | .imm-exec input.mnemonic { 100 | width:200px; 101 | } 102 | 103 | .data-recorder { 104 | background:#333; 105 | color:#ccc; 106 | font-weight:bold; 107 | font-family: Arial; 108 | min-width:690px; 109 | } 110 | .MZ-700 .data-recorder button { 111 | border-color:#444; 112 | background-color:#555; 113 | color:silver; 114 | } 115 | .MZ-700 .data-recorder button:hover { 116 | color:white; 117 | background-color:#000000; 118 | } 119 | .MZ-700 .data-recorder button:disabled { 120 | color:gray; 121 | background-color:#666666; 122 | } 123 | .MZ-700 .data-recorder .cmt-message { 124 | display: inline-block; 125 | min-width:100px; 126 | background-color:#eee; 127 | color:#222; 128 | padding:5px; 129 | border-radius:3px; 130 | text-align: center; 131 | } 132 | .MZ-700 .data-recorder .cmt-message a { 133 | display: inline-block; 134 | border-radius:5px; 135 | background-color: blue; 136 | color:white; 137 | margin-left:10px; 138 | padding:2px; 139 | font-size:9pt; 140 | text-decoration:none; 141 | cursor: pointer; 142 | } 143 | .MZ-700 .data-recorder .cmt-message a.download-link:before { 144 | content:"▼MZT"; 145 | } 146 | .MZ-700 button.toggle.on 147 | { 148 | background-color: white; 149 | color: blue; 150 | } 151 | .MZ-700 button.toggle.off 152 | { 153 | background-color: blue; 154 | color: white; 155 | } 156 | .MZ-700 button.imaged { 157 | width:30px; 158 | height:30px; 159 | padding:0; 160 | border-width: 0; 161 | vertical-align: bottom; 162 | } 163 | .MZ-700 button.imaged img { 164 | width:30px; 165 | height:30px; 166 | padding:0; 167 | margin:0; 168 | border-width: 0; 169 | } 170 | 171 | /* Layout */ 172 | .dock.resizer { 173 | background-color: white; 174 | } 175 | .dock.resizer:hover { 176 | background-color: #ccc; 177 | } 178 | .dock.resizer.resizing { 179 | background-color: blue; 180 | } 181 | .dock.horizontal.resizer { 182 | cursor: ew-resize; 183 | } 184 | .dock.vertical.resizer { 185 | cursor: ns-resize; 186 | } -------------------------------------------------------------------------------- /MZ-700/mz700-emu.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* tslint:disable:no-console */ 3 | import TransWorker from "transworker"; 4 | import MZ700 from "./mz700"; 5 | import MZ700Scrn from "../lib/mz700-scrn"; 6 | import MZ700WebUI from "./mz700-web-ui"; 7 | 8 | async function main() { 9 | // Create MZ-700 Emulator 10 | const mz700js = TransWorker.createInterface( 11 | "./js/mz700-worker.min.js", MZ700, 12 | { syncType: TransWorker.SyncTypePromise }); 13 | 14 | // MZ-700 Screen 15 | const mz700screen = document.querySelector("#mz700screen") as HTMLElement; 16 | const mz700scrn = new MZ700Scrn(mz700screen); 17 | const canvas = document.createElement("CANVAS") as HTMLCanvasElement; 18 | mz700screen.style.display = "none"; 19 | mz700screen.appendChild(canvas); 20 | mz700scrn.create({canvas}); 21 | canvas.style.height = "calc(100% - 1px)"; 22 | 23 | mz700scrn.setupRendering(); 24 | const mz700screen2 = document.createElement("DIV") as HTMLElement; 25 | const mz700scrn2 = new MZ700Scrn(mz700screen2); 26 | const canvas2 = document.createElement("CANVAS") as HTMLCanvasElement; 27 | mz700screen2.style.display = "none"; 28 | mz700screen2.appendChild(canvas2); 29 | mz700scrn2.create({canvas: canvas2}); 30 | mz700js.transferObject("offscreenCanvas", 31 | canvas2.transferControlToOffscreen()); 32 | mz700js.subscribe("onUpdateScrn", (imageData:ImageData) => { 33 | mz700scrn._ctx.putImageData(imageData, 0, 0); 34 | }); 35 | 36 | // Create user interface 37 | await MZ700WebUI.createUI(mz700js, mz700screen, canvas); 38 | } 39 | 40 | main().catch(err => console.warn(err.stack)); 41 | -------------------------------------------------------------------------------- /MZ-700/mz700-new-monitor.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* tslint:disable:class-name */ 3 | import MemoryBlock from "../Z80/memory-block"; 4 | export default class MZ700_NewMonitor extends MemoryBlock { 5 | constructor() { 6 | super(); 7 | } 8 | create():void { 9 | MemoryBlock.prototype.create.call(this, { startAddr: 0x0000, size: 0x1000 }); 10 | } 11 | setBinary(bin:number[]):void { 12 | for (let i = 0; i < this.size; i++) { 13 | const address = this.startAddr + i; 14 | MemoryBlock.prototype.pokeByte.call(this, 15 | address, bin[address]); 16 | } 17 | } 18 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 19 | pokeByte(address:number, value:number):void { 20 | /* IGNORE ALL WRITING */ 21 | } 22 | } 23 | module.exports = MZ700_NewMonitor; 24 | -------------------------------------------------------------------------------- /MZ-700/mz700-worker.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Codes for Worker context. 3 | // Override the methods in Worker context 4 | // 5 | "use strict"; 6 | /* tslint:disable:no-console */ 7 | import TransWorker = require('transworker'); 8 | import MZ700 from './mz700'; 9 | import MZ700CanvasRenderer from '../lib/mz700-canvas-renderer'; 10 | import MZ700CG from "../lib/mz700-cg.js"; 11 | import PCG700 from "../lib/PCG-700.js"; 12 | 13 | function createMZ700(transworker, mz700CanvasRenderer) { 14 | const mz700 = new MZ700(); 15 | let vramUpdated = true; 16 | const onVramUpdate = () => { 17 | vramUpdated = true; 18 | }; 19 | mz700.create({ 20 | started: () => transworker.postNotify("start"), 21 | stopped: () => transworker.postNotify("stop"), 22 | onBreak: () => transworker.postNotify("onBreak"), 23 | onVramUpdate: (index, dispcode, attr) => { 24 | mz700CanvasRenderer.writeVram(index, attr, dispcode); 25 | onVramUpdate(); 26 | }, 27 | startSound: freq => transworker.postNotify("startSound", [ freq ]), 28 | stopSound: () => transworker.postNotify("stopSound"), 29 | onStartDataRecorder: () => transworker.postNotify("onStartDataRecorder"), 30 | onStopDataRecorder: () => transworker.postNotify("onStopDataRecorder"), 31 | }); 32 | 33 | transworker.listenTransferableObject("offscreenCanvas", offscreenCanvas => { 34 | mz700CanvasRenderer.create({ 35 | canvas: offscreenCanvas, 36 | CG: new MZ700CG(MZ700CG.ROM, 8, 8), 37 | }); 38 | mz700CanvasRenderer.setupRendering(); 39 | setInterval(()=>{ 40 | if(vramUpdated) { 41 | const imageData = mz700CanvasRenderer.getImageData(); 42 | transworker.postNotify("onUpdateScrn", imageData, [imageData.data.buffer]); 43 | vramUpdated = false; 44 | } 45 | }, 1000/24); 46 | }); 47 | return mz700; 48 | } 49 | 50 | async function main() { 51 | const transworker = new TransWorker(); 52 | 53 | const mz700CanvasRenderer = new MZ700CanvasRenderer(); 54 | const mz700 = createMZ700(transworker, mz700CanvasRenderer); 55 | const pcg700 = new PCG700(mz700CanvasRenderer); 56 | mz700.attachPCG700(pcg700); 57 | 58 | transworker.create(mz700); 59 | } 60 | main().catch(err => console.error(`Error: ${err.stack}`)); 61 | -------------------------------------------------------------------------------- /MZ-700/pcg700-sample.asm: -------------------------------------------------------------------------------- 1 | ORG 1200H 2 | ;=========================================== 3 | ; 4 | ; CHANGE THE CURSOR PATTERN WITH PCG-700 5 | ; 6 | ;=========================================== 7 | PCGSMPL:ENT 8 | CALL ENPCG ;ENABLE PCG-700 9 | LD B,00H ;PCG PAGE 0 10 | LD C,EFH ;DISPLAY CODE OF CURSOR 11 | LD HL,CGPAT ;TOP ADDRESS OF PATTERN DATA 12 | CALL SETPCG ;WRITE PATTERN 13 | RST 00H ;RESET 14 | NOP 15 | NOP 16 | ;=========================================== 17 | ; 18 | ; RESTORE BUILT-IN CURSOR PATTERN 19 | ; 20 | ;=========================================== 21 | RSTRPCG:ENT 22 | CALL DIPCG ;ENABLE PCG-700 23 | RST 00H ;RESET 24 | ; 25 | ; 26 | ; 27 | ;=========================================== 28 | ;CURSOR PATTERN DATA 29 | ;=========================================== 30 | CGPAT: ENT 31 | DEFB 88H ;10001000 32 | DEFB D8H ;11011000 33 | DEFB A8H ;10101000 34 | DEFB 88H ;10001000 35 | DEFB 1FH ;00011111 36 | DEFB 06H ;00000110 37 | DEFB 0CH ;00001100 38 | DEFB 1FH ;00011111 39 | ; 40 | ; 41 | ; 42 | ;=========================================== 43 | ; ENABLE PCG-700 44 | ;=========================================== 45 | ENPCG: ENT 46 | PUSH HL 47 | LD HL, E012H 48 | LD (HL), 10H 49 | POP HL 50 | RET 51 | ; 52 | ; 53 | ; 54 | ;=========================================== 55 | ; DISABLE PCG-700 56 | ;=========================================== 57 | DIPCG: ENT 58 | PUSH HL 59 | LD HL, E012H 60 | LD (HL), 18H 61 | POP HL 62 | RET 63 | ; 64 | ; 65 | ; 66 | ;=========================================== 67 | ; SETPCG 68 | ; 69 | ; B: SELECT CGRAM PAGE 0 or 1 70 | ; 0 : PAGE 0 71 | ; NOT 0 : PAGE 1 72 | ; C: DISPLAY CODE (80H - FFH) 73 | ; HL: TOP ADDRESS OF 8 BYTES CHAR PATTERN 74 | ;=========================================== 75 | SETPCG: ENT 76 | PUSH AF 77 | PUSH BC 78 | PUSH DE 79 | 80 | ;IF B!=0 THEN SEPADR 81 | LD A,B 82 | OR A 83 | JR NZ,SEPADR 84 | 85 | ;OFFSET C FOR PAGE0 86 | LD A,C 87 | SUB A,80H 88 | LD C,A 89 | 90 | SEPADR: ENT 91 | ; D: UPPER ADDRESS 00H - 07H 92 | ; E: LOWER ADDRESS 00H - FFH 93 | LD D,00H 94 | LD E,C 95 | LD C,03H 96 | SFTADR: ENT 97 | SLA E 98 | RL D 99 | DEC C 100 | JR NZ,SFTADR 101 | 102 | LD B,08H ;TRANSFER LOOP COUNT 103 | 104 | ; 105 | ;PATTERN TRANSFER LOOP 106 | ; 107 | WRPAT: ENT 108 | 109 | LD A,(HL) ;LOAD PATTERN 110 | PUSH HL ;SAVE PATTERN ADDRESS 111 | LD HL,E010H ;SET PATTERN TO PCG-700 112 | LD (HL),A 113 | LD HL,E011H ;SET LOWER ADDRESS TO PCG 114 | LD (HL),E 115 | 116 | LD HL,E012H ;SET UPPER ADDRESS AND 117 | LD A,10H ;WRITE PATTERN TO PCG-700 118 | OR D 119 | LD (HL),A ;WE=1,ADR=D 120 | LD (HL),D ;WE=0,ADR=D 121 | 122 | POP HL ;RESTORE PATTERN ADDRSS 123 | INC HL ;SRC ADDR 124 | INC E ;DST ADDR 125 | DEC B ;LOOP COUNTER 126 | JR NZ,WRPAT ;UNTIL ZERO 127 | 128 | POP DE 129 | POP BC 130 | POP AF 131 | RET 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MZ-700 Full JavaScript Emulator 2 | =============================== 3 | 4 | [![Build Status](https://travis-ci.org/takamin/mz700-js.svg?branch=master)](https://travis-ci.org/takamin/mz700-js) 5 | 6 | 8 | 10 | 11 | 12 | Description 13 | ----------- 14 | 15 | This is an emulator of "MZ-700", a Japanese historical 8-bit microcomputer. 16 | 17 | This emulator is written by JavaScript. 18 | It works on the modern HTML5 web browser. 19 | I would strongly recommend Google Chrome, 20 | because of the emulation speed and its stability. 21 | 22 | MZ-700 23 | ------ 24 | 25 | The MZ-700 is produced by SHARP in Nov.15,1982. 26 | It equipped a Z80A CPU 3.58MHz, 27 | and represents various characters in eight colors 28 | and a monoral beep sound, but no graphics. 29 | 30 | There were three models: 31 | 32 | * MZ-711 - The base model. 33 | * MZ-721 - A built-in cassette deck is available 34 | * MZ-731 - A cassette deck and 4 color plotter printer were built in. 35 | 36 | Many people were saying, 37 | 38 | __"MZ-700 Has No LIMIT"__ 39 | 40 | 42 | 44 | 45 | 46 | PREREQUISITES and FEATURES 47 | -------------------------- 48 | 49 | * Node.js 8.10 or later is required. 50 | * This emulator bundles [MZ-NEW MONITOR](http://retropc.net/mz-memories/mz700/) to boot. 51 | * You can drop a MZT-file to the screen to run. 52 | * Z80 assembler and disassembler is available on the Web and also CLI command. 53 | * And, it's a somewhat a crazy feature, the emulator running on the CLI with Node.js is also available. 54 | 55 | INSTALLATION 56 | ------------ 57 | 58 | ``` 59 | $ npm install # Build 60 | $ npm start # Start local web server and run the app. 61 | ``` 62 | 63 | Access http://localhost:3000/mz700-js/emu.html with your browser, 64 | if the emulation page does not open. 65 | 66 | Or [the emulation page](https://takamin.github.io/mz700-js/emu.html) is available 67 | without local installation. 68 | 69 | 70 | LICENCE 71 | ------- 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /Z80/bin-util.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* tslint:disable:no-bitwise */ 4 | 5 | export default class Z80BinUtil { 6 | static pair(h:number, l:number):number { 7 | return ((0xff & h) << 8) + (0xff & l); 8 | } 9 | static hibyte(nn:number):number { 10 | return (nn >> 8) & 0xff; 11 | } 12 | static lobyte(nn:number):number { 13 | return nn & 0xff; 14 | } 15 | static getSignedByte(e:number):number { 16 | e = e & 0xff; 17 | if(e & 0x80) { 18 | e = ((~e) & 0xff) + 1; 19 | return -e; 20 | } 21 | return e; 22 | } 23 | } 24 | module.exports = Z80BinUtil; -------------------------------------------------------------------------------- /Z80/imem.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import Z80BinUtil from "./bin-util"; 3 | 4 | /* tslint:disable:no-console */ 5 | 6 | /** 7 | * IMem - Z80 emulator's memory interface 8 | * @constructor 9 | */ 10 | export default class IMem { 11 | size:number; 12 | startAddr:number; 13 | constructor() { 14 | /* empty */ 15 | } 16 | /** 17 | * Create 18 | * @param {any} opt the options. 19 | * @returns {undefined} 20 | */ 21 | create(opt?:{size?:number, startAddr?:number}):void { 22 | opt = opt || {}; 23 | this.size = opt.size || 0x10000; 24 | this.startAddr = opt.startAddr || 0; 25 | if (this.startAddr < 0 || this.startAddr > 0xffff) { 26 | throw new Error("Invalid start address of memory"); 27 | } 28 | if (this.size < 0) { 29 | throw new Error("Invalid memory size"); 30 | } 31 | if (this.startAddr + this.size > 0x10000) { 32 | throw new Error("Invalid combination of start address and memory size."); 33 | } 34 | } 35 | /** 36 | * Read a byte data. 37 | * This peekByte is an abstruct called from `peek`. 38 | * @param {number} address an address. 39 | * @returns {number} the value in the memory. 40 | */ 41 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 42 | peekByte(address:number):number { 43 | const msg = "Error: abstruct pokeByte was invoked." + 44 | `This method must be overrided by the class ${this.constructor.name}`; 45 | console.error(msg); 46 | throw new Error(msg); 47 | return 0; 48 | } 49 | /** 50 | * Write a byte data. 51 | * This pokeByte is an abstruct called from `poke`. 52 | * @param {number} address an address. 53 | * @param {number} value a data. 54 | * @returns {undefined} 55 | */ 56 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 57 | pokeByte(address:number, value:number):void { 58 | const msg = "Error: abstruct pokeByte was invoked." + 59 | `This method must be overrided by the class ${this.constructor.name}`; 60 | console.error(msg); 61 | throw new Error(msg); 62 | } 63 | /** 64 | * Clear memory by zero. 65 | * @returns {undefined} 66 | */ 67 | clear():void { 68 | for (let i = 0; i < this.size; i++) { 69 | this.pokeByte(i, 0); 70 | } 71 | } 72 | /** 73 | * Read a byte data. 74 | * @param {number} address an address. 75 | * @returns {number} the value in the memory. 76 | */ 77 | peek(address:number):number { 78 | return this.peekByte(address); 79 | } 80 | /** 81 | * Write a byte data. 82 | * @param {number} address an address. 83 | * @param {number} value a data. 84 | * @returns {undefined} 85 | */ 86 | poke(address:number, value:number):void { 87 | this.pokeByte(address, value); 88 | } 89 | /** 90 | * Read a 16bit data. 91 | * @param {number} address an address. 92 | * @returns {number} the value in the memory. 93 | */ 94 | peekPair(address:number):number { 95 | return Z80BinUtil.pair(this.peek(address + 1), this.peek(address + 0)); 96 | } 97 | } 98 | 99 | module.exports = IMem; 100 | -------------------------------------------------------------------------------- /Z80/memory-bank.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import IMem from "./imem"; 3 | 4 | /* tslint:disable:no-bitwise */ 5 | 6 | /** 7 | * MemoryBank 8 | * @constructor 9 | * @param {object} opt the options. 10 | */ 11 | export default class MemoryBank extends IMem { 12 | mem:(IMem|Record)[]; 13 | memblk:Map; 14 | constructor(opt?:{size?:number, startAddr?:number}) { 15 | super(); 16 | this.create(opt); 17 | } 18 | /** 19 | * Create 20 | * @param {any} opt the options. 21 | * @returns {undefined} 22 | */ 23 | create(opt?:{size?:number, startAddr?:number}):void { 24 | super.create(opt); 25 | this.mem = new Array(this.size); 26 | this.memblk = new Map(); 27 | } 28 | /** 29 | * Set named memory block. 30 | * @param {string} name A name of a memory bank. 31 | * @param {IMem} memblk A memory block. 32 | * @returns {undefined} 33 | */ 34 | setMemoryBlock(name:string, memblk:IMem):void { 35 | if (memblk == null) { 36 | if (this.memblk.has(name)) { 37 | const mem = this.memblk.get(name); 38 | const size = mem.size; 39 | const startAddr = mem.startAddr; 40 | const endAddr = startAddr + size; 41 | const nullMem = { peek: () => 0, poke: () => { /* empty */ } }; 42 | for (let j = startAddr; j < endAddr; j++) { 43 | this.mem[j] = nullMem; 44 | } 45 | this.memblk.delete(name); 46 | } 47 | } 48 | else { 49 | this.memblk.set(name, memblk); 50 | const mem = this.memblk.get(name); 51 | const size = mem.size; 52 | const startAddr = mem.startAddr; 53 | const endAddr = startAddr + size; 54 | for (let j = startAddr; j < endAddr; j++) { 55 | this.mem[j] = memblk; 56 | } 57 | } 58 | } 59 | /** 60 | * Read a byte data. 61 | * @param {number} address an address. 62 | * @returns {number} the value in the memory. 63 | */ 64 | peekByte(address:number):number { 65 | return (this.mem[address - this.startAddr] as IMem).peek(address) & 0xff; 66 | } 67 | /** 68 | * Write a byte data. 69 | * @param {number} address an address. 70 | * @param {number} value a data. 71 | * @returns {undefined} 72 | */ 73 | pokeByte(address:number, value:number):void { 74 | (this.mem[address - this.startAddr] as IMem).poke(address, value & 0xff); 75 | } 76 | } 77 | 78 | module.exports = MemoryBank; 79 | -------------------------------------------------------------------------------- /Z80/memory-block-cbrw.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import MemoryBlockCbw from "./memory-block-cbw"; 3 | 4 | /** 5 | * MemoryBlock 6 | * @constructor 7 | * @param {object} opt the options. 8 | */ 9 | export default class MemoryBlockCbrw extends MemoryBlockCbw { 10 | /* callback on peek */ 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | onPeek = (addr:number, value:number):number => (0); 13 | constructor(opt?:{ 14 | size?:number, 15 | startAddr?:number, 16 | onPeek?:(addr:number, value:number)=>number, 17 | onPoke?:(addr:number, value:number)=>void, 18 | }) { 19 | super(opt); 20 | if(opt.onPeek) { 21 | this.onPeek = opt.onPeek; 22 | } 23 | } 24 | 25 | /** 26 | * Read a byte data. 27 | * @param {number} address an address. 28 | * @returns {number} the value in the memory. 29 | */ 30 | peek(address:number):number { 31 | const value:number = super.peekByte(address); 32 | const override = this.onPeek(address, value); 33 | if (override != null && override !== undefined) { 34 | return override; 35 | } 36 | return value; 37 | } 38 | } 39 | 40 | module.exports = MemoryBlockCbrw; 41 | -------------------------------------------------------------------------------- /Z80/memory-block-cbw.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import MemoryBlock from "./memory-block"; 3 | 4 | /** 5 | * MemoryBlock 6 | * @class 7 | */ 8 | export default class MemoryBlockCbw extends MemoryBlock { 9 | /* callback on poke */ 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | onPoke = (addr:number, value:number):void => { /* empty */ }; 12 | /** 13 | * @constructor 14 | * @param {object} opt the options. 15 | */ 16 | constructor(opt?:{size?:number, startAddr?:number, onPoke?:(addr:number, value:number)=>void}) { 17 | super(opt); 18 | if(opt.onPoke) { 19 | this.onPoke = opt.onPoke; 20 | } 21 | } 22 | 23 | /** 24 | * Write a byte data. 25 | * @param {number} address an address. 26 | * @param {number} value a data. 27 | * @returns {undefined} 28 | */ 29 | poke(address:number, value:number):void { 30 | super.pokeByte(address, value); 31 | this.onPoke(address, this.peekByte(address)); 32 | } 33 | } 34 | 35 | module.exports = MemoryBlockCbw; 36 | -------------------------------------------------------------------------------- /Z80/memory-block.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import IMem from "./imem"; 3 | 4 | /** 5 | * MemoryBlock 6 | * @constructor 7 | * @param {object} opt the options. 8 | */ 9 | export default class MemoryBlock extends IMem { 10 | mem:number[]; 11 | constructor(opt?:{size?:number, startAddr?:number}) { 12 | super(); 13 | super.create(opt); 14 | this.mem = new Array(this.size); 15 | } 16 | /** 17 | * Read a byte data. 18 | * @param {number} address an address. 19 | * @returns {number} the value in the memory. 20 | */ 21 | peekByte(address:number):number { 22 | return this.mem[address - this.startAddr]; 23 | } 24 | /** 25 | * Write a byte data. 26 | * @param {number} address an address. 27 | * @param {number} value a data. 28 | * @returns {undefined} 29 | */ 30 | pokeByte(address:number, value:number):void { 31 | this.mem[address - this.startAddr] = value; 32 | } 33 | } 34 | 35 | module.exports = MemoryBlock; 36 | -------------------------------------------------------------------------------- /bin/bin2mzt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var MZ_TapeHeader = require('../js/lib/mz-tape-header.js'); 3 | var changeExt = require('../js/lib/change-ext.js'); 4 | var fs = require('fs'); 5 | var getPackageJson = require("./lib/get-package-json"); 6 | var npmInfo = getPackageJson(__dirname + "/.."); 7 | var Getopt = require('node-getopt'); 8 | var getopt = new Getopt([ 9 | ['a', 'loading-address=ADDR', 'set loading address'], 10 | ['e', 'execution-address=ADDR', 'set execution address'], 11 | ['o', 'output-file=FILENAME', 'filename to output'], 12 | ['h', 'help', 'display this help'], 13 | ['v', 'version', 'show version'] 14 | ]); 15 | var cli = getopt.parseSystem(); 16 | var description = "A MZT header generator. -- " + npmInfo.name + "@" + npmInfo.version; 17 | getopt.setHelp( 18 | "Usage: bin2mzt [OPTION] filename\n" + 19 | description + "\n" + 20 | "\n" + 21 | "[[OPTIONS]]\n" + 22 | "\n" + 23 | "Installation: npm install -g mz700-js\n" + 24 | "Repository: https://github.com/takamin/mz700-js"); 25 | 26 | if(cli.options.help) { 27 | getopt.showHelp(); 28 | process.exit(0); 29 | } 30 | 31 | if(cli.options.version) { 32 | console.log(description); 33 | process.exit(0); 34 | } 35 | 36 | var args = require("hash-arg").get(["input_filename"], cli.argv); 37 | if(cli.argv.length < 1) { 38 | console.error('error: no input file'); 39 | process.exit(-1); 40 | } 41 | var input_filename = args.input_filename; 42 | var output_filename = null; 43 | if('output-file' in cli.options) { 44 | output_filename = cli.options['output-file']; 45 | } else { 46 | output_filename = changeExt(input_filename, ".mzt"); 47 | } 48 | 49 | // 50 | // MZT-Header 51 | // 52 | var mzt_header = MZ_TapeHeader.createNew(); 53 | var load_addr = 0; 54 | var exec_addr = 0; 55 | if('loading-address' in cli.options) { 56 | load_addr = parseInt(cli.options['loading-address'], 0); 57 | exec_addr = load_addr; 58 | } 59 | if('execution-address' in cli.options) { 60 | exec_addr = parseInt(cli.options['execution-address'], 0); 61 | } 62 | fs.readFile(input_filename, function(err, data) { 63 | if(err) { 64 | throw err; 65 | } 66 | const buffer = Buffer.from(data); 67 | mzt_header.setFilename(output_filename); 68 | mzt_header.setAddrLoad(load_addr); 69 | mzt_header.setAddrExec(exec_addr); 70 | mzt_header.setFilesize(buffer.length); 71 | fs.writeFileSync(output_filename, 72 | Buffer.concat([Buffer.from(mzt_header.buffer), buffer])); 73 | }); 74 | -------------------------------------------------------------------------------- /bin/cli-command/breakpoint.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | var { parseAddress } = require("../../js/lib/parse-addr"); 4 | var { HEX } = require("../../js/lib/number-util"); 5 | module.exports = new CliCommand("bp", function(mz700, args) { 6 | var bp = mz700.getBreakPoints(); 7 | if(args.length > 0) { 8 | var bp_cmd = args[0]; 9 | if(bp_cmd != "set" && bp_cmd != "rm" && bp_cmd != "clear") { 10 | console.log("Error: Unrecognized", bp_cmd); 11 | return false; 12 | } 13 | if(bp_cmd == "clear") { 14 | if(args.length > 1) { 15 | console.log("Error: No a parameter for 'clear'"); 16 | return false; 17 | } 18 | mz700.clearBreakPoints(); 19 | return; 20 | } 21 | var address_list = args.slice(1); 22 | address_list.forEach(function(addrTok) { 23 | var addr = parseAddress(addrTok); 24 | if(bp_cmd == "set") { 25 | mz700.addBreak(addr, 1, null); 26 | } else if(bp_cmd == "rm") { 27 | mz700.removeBreak(addr, 1, null); 28 | } 29 | }); 30 | } 31 | bp.forEach(function(state, addr) { 32 | if(state != null) { 33 | console.log( 34 | "ADDR:" + HEX(parseInt(addr), 4) + "H", 35 | state); 36 | } 37 | }); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /bin/cli-command/cmt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | var mztReadFile = require("./mzt-read-file"); 4 | var MZ_TapeHeader = require('../../js/lib/mz-tape-header.js'); 5 | var { HEX } = require("../../js/lib/number-util"); 6 | 7 | module.exports = new CliCommand("cmt", (mz700, args) => { 8 | switch(args[0]) { 9 | case "set": 10 | if(args.length < 2) { 11 | console.log("Blank CMT is set"); 12 | mz700.setCassetteTape([]); 13 | } else { 14 | var filename = args.slice(1).join(' '); 15 | return mztReadFile(filename).then(function(mzt_list) { 16 | var setCMT = false; 17 | if(mzt_list != null && mzt_list.length > 0) { 18 | mzt_list.forEach(function(mzt, i) { 19 | console.log("[" + (i + 1) + "/" + mzt_list.length + "] " + 20 | HEX(mzt.header.addrLoad, 4) + "h --- " + 21 | HEX(mzt.header.addrLoad + mzt.header.fileSize - 1, 4) + "h " + 22 | "(" + mzt.header.fileSize + " bytes), " + 23 | HEX(mzt.header.addrExec, 4) + "h, " + mzt.header.filename); 24 | if(!setCMT) { 25 | var bytes = mzt.header.buffer.concat(mzt.body.buffer); 26 | mz700.setCassetteTape(bytes); 27 | setCMT = true; 28 | } 29 | }); 30 | } 31 | }); 32 | } 33 | break; 34 | case "eject": 35 | if(args.length > 1) { 36 | console.log("Error: No parameter needed for cmt eject."); 37 | return false; 38 | } else { 39 | var bytes = mz700.dataRecorder_ejectCmt(); 40 | if(bytes == null || bytes.length < 128) { 41 | console.log("ejected, but no data was found in cmt."); 42 | return; 43 | } 44 | var header = new MZ_TapeHeader(bytes, 0); 45 | console.log("Tape data: " + 46 | HEX(header.addrLoad, 4) + "-" + 47 | HEX(header.addrLoad + header.fileSize - 1, 4) + "(" + 48 | header.fileSize + " bytes), Start with" + 49 | HEX(header.addrExec, 4) + "," + 50 | header.filename); 51 | } 52 | break; 53 | case "play": 54 | if(args.length > 1) { 55 | console.log("Error: No parameter needed for cmt play."); 56 | return false; 57 | } else { 58 | mz700.dataRecorder_pushPlay(); 59 | } 60 | break; 61 | case "rec": 62 | if(args.length > 1) { 63 | console.log("Error: No parameter needed for cmt rec."); 64 | return false; 65 | } else { 66 | mz700.dataRecorder_pushRec(); 67 | } 68 | break; 69 | case "stop": 70 | if(args.length > 1) { 71 | console.log("Error: No parameter needed for cmt stop."); 72 | return false; 73 | } else { 74 | mz700.dataRecorder_pushStop(); 75 | } 76 | break; 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /bin/cli-command/command.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function CliCommand(name, func) { 3 | this.name = name; 4 | this.func = func; 5 | this._commandEngine = null; 6 | 7 | this.tasklist = []; 8 | this._commands = {}; 9 | } 10 | CliCommand.prototype.create = function(name, func) { 11 | this.name = name; 12 | this.func = func; 13 | return this; 14 | }; 15 | CliCommand.prototype.install = function(command) { 16 | if(Array.isArray(command)) { 17 | command.forEach(item => { 18 | this.install(item); 19 | }); 20 | } else { 21 | command.installTo(this); 22 | } 23 | return command; 24 | }; 25 | CliCommand.prototype.installTo = function(commandTable) { 26 | console.log("cli command " + this.name); 27 | commandTable._commands[this.name] = this; 28 | this._commandEngine = commandTable; 29 | }; 30 | CliCommand.prototype.putPrompt = function(ok) { 31 | if(ok) { 32 | console.log("OK."); 33 | console.log(""); 34 | } 35 | process.stdout.write('command > '); 36 | }; 37 | CliCommand.prototype.executeCommandline = function(line, mz700) { 38 | var commandTokens = line.split(/\s/); 39 | if(commandTokens[0] == '') { 40 | this.putPrompt(false); 41 | return; 42 | } 43 | var command = this.searchCommand(commandTokens); 44 | this.executeCommand(command, mz700, line); 45 | }; 46 | CliCommand.prototype.searchCommand = function(commandline) { 47 | var _commands = this._commands; 48 | var running = true; 49 | while(running) { 50 | const [ command, ...args ] = commandline; 51 | if(!command) { 52 | this.putPrompt(false); 53 | return { entry: null, func: null, args: null }; 54 | } 55 | if(command in _commands) { 56 | var def = _commands[command]; 57 | if(def.constructor.name == "CliCommand" || 58 | "func" in def && 59 | typeof(def.func) == "function") 60 | { 61 | return { entry: def, func: def.func, args: args }; 62 | } else if(typeof(def) == 'function') { 63 | return { entry: null, func: def, args: args }; 64 | } else if(typeof(def) == 'object') { 65 | _commands = def; 66 | commandline = args; 67 | } else { 68 | running = false; 69 | } 70 | } else { 71 | running = false; 72 | } 73 | } 74 | return null; 75 | }; 76 | CliCommand.prototype.executeCommand = function(command, mz700, commandline) { 77 | if(command != null && command.func != null) { 78 | this.tasklist.push(() => command.func.call( 79 | command.entry, mz700, command.args)); 80 | } else { 81 | this.tasklist.push(() => { 82 | console.log("Error: Unrecognized command: ", commandline); 83 | return false; 84 | }); 85 | } 86 | }; 87 | CliCommand.prototype.executeSubCommand = function(args, mz700) { 88 | var command = this.searchCommand(args); 89 | if(command != null) { 90 | command.entry = this; 91 | } 92 | this._commandEngine.executeCommand( 93 | command, mz700, 94 | this.name+ " " + args.join(' ')); 95 | 96 | return false; 97 | }; 98 | CliCommand.prototype.runCli = function() { 99 | const procCommandLine = async () => { 100 | if(this.tasklist.length > 0) { 101 | while(this.tasklist.length > 0) { 102 | const task = this.tasklist.shift(); 103 | const promise = task(); 104 | if(promise && promise.constructor === Promise) { 105 | try { 106 | await promise; 107 | } catch(err) { 108 | console.log(err); 109 | } 110 | } 111 | } 112 | this.putPrompt(true); 113 | } 114 | setTimeout(procCommandLine, 50); 115 | } 116 | this.putPrompt(true); 117 | procCommandLine(); 118 | }; 119 | module.exports = CliCommand; 120 | -------------------------------------------------------------------------------- /bin/cli-command/conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | function CliCommandConf() { 4 | this._sendKey = null; 5 | this._commands = { 6 | "key" : { 7 | "duration": { 8 | "make": function(mz700, args) { 9 | if(args.length == 0) { 10 | console.log("conf key duration make: " + 11 | this._sendKey._durationMake + 12 | " [ms]"); 13 | } else { 14 | this._sendKey._durationMake = 15 | parseInt(args[0]); 16 | } 17 | }, 18 | "release": function(mz700, args) { 19 | if(args.length == 0) { 20 | console.log("conf key duration release: " + 21 | this._sendKey._durationRelease + 22 | " [ms]"); 23 | } else { 24 | this._sendKey._durationRelease = 25 | parseInt(args[0]); 26 | } 27 | } 28 | } 29 | } 30 | }; 31 | } 32 | CliCommandConf.prototype = new CliCommand("conf", 33 | function(mz700, args) { 34 | return this.executeSubCommand(args, mz700, args); 35 | } 36 | ); 37 | CliCommandConf.prototype.installTo = function(table) { 38 | this._sendKey = table._commands.key; 39 | CliCommand.prototype.installTo.call(this, table); 40 | }; 41 | module.exports = new CliCommandConf(); 42 | -------------------------------------------------------------------------------- /bin/cli-command/exit.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | module.exports = new CliCommand("exit", function(mz700/*, args*/) { 4 | mz700.stop(); 5 | process.exit(0); 6 | }); 7 | -------------------------------------------------------------------------------- /bin/cli-command/jump.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | var { parseAddress } = require("../../js/lib/parse-addr"); 4 | module.exports = new CliCommand("jp", function(mz700, args) { 5 | if(args.length < 1) { 6 | console.log("Error: No address specified; 'jp 1234h'"); 7 | return false; 8 | } 9 | var addrTok = args[0]; 10 | var addr = parseAddress(addrTok); 11 | mz700.setPC(addr); 12 | }); 13 | -------------------------------------------------------------------------------- /bin/cli-command/mem.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | var { parseAddress } = require("../../js/lib/parse-addr"); 4 | var { HEX } = require("../../js/lib/number-util"); 5 | module.exports = new CliCommand("mem", function(mz700, args) { 6 | var addr = null; 7 | switch(args[0]) { 8 | case "set": 9 | if(args.length < 2) { 10 | console.log("Error: no address specified"); 11 | return false; 12 | } else { 13 | addr = parseAddress(args[1]); 14 | var data_list = args.slice(2); 15 | data_list.forEach(function(data, i) { 16 | mz700.memory.poke(addr + i, parseAddress(data)); 17 | }); 18 | } 19 | break; 20 | case "dump": 21 | if(args.length < 2) { 22 | console.log("Error: no address specified"); 23 | } else { 24 | addr = parseAddress(args[1]); 25 | var len = 256; 26 | var cols = 16; 27 | var hexArr = []; 28 | var put = function() { 29 | var s = hexArr.join(""); 30 | console.log(s); 31 | hexArr = []; 32 | } 33 | for(var i = 0; i < len; i++) { 34 | var data = mz700.memory.peek(addr + i); 35 | if(i % cols == 0) { 36 | hexArr.push(HEX(addr+i, 4)); 37 | hexArr.push(":"); 38 | } 39 | if(i % cols != 0 && i % (cols / 2) == 0) { 40 | hexArr.push(" -"); 41 | } 42 | hexArr.push(" "); 43 | hexArr.push(HEX(data, 2)); 44 | if(i % cols == cols - 1) { 45 | put(); 46 | } 47 | } 48 | if(hexArr.length > 0) { 49 | put(); 50 | } 51 | } 52 | break; 53 | default: 54 | console.log("Error: Unrecognized command " + args[0]); 55 | return false; 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /bin/cli-command/mzt-read-file.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fs = require('fs'); 3 | const MZ_Tape = require("../../js/lib/mz-tape.js"); 4 | module.exports = function (filename) { 5 | return new Promise((resolve, reject) => { 6 | fs.readFile(filename, (err, data) => { 7 | if(err) { 8 | reject(err); 9 | } else { 10 | console.log("Loading " + filename + " ... "); 11 | const mzt_list = MZ_Tape.parseMZT(data); 12 | if(mzt_list == null || !Array.isArray(mzt_list) || mzt_list.length == 0) { 13 | reject(new Error(`No MZT header read.`)); 14 | } else { 15 | resolve(mzt_list); 16 | } 17 | } 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /bin/cli-command/register.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const NumberUtil = require("../../js/lib/number-util.js"); 3 | var CliCommand = require("./command"); 4 | module.exports = new CliCommand("reg", function(mz700/*, args*/) { 5 | var [reg] = mz700.getRegister(); 6 | console.log([ 7 | `HL:${NumberUtil.HEX(reg.H, 2)}${NumberUtil.HEX(reg.L, 2)}H`, 8 | `BC:${NumberUtil.HEX(reg.B, 2)}${NumberUtil.HEX(reg.C, 2)}H`, 9 | `DE:${NumberUtil.HEX(reg.D, 2)}${NumberUtil.HEX(reg.E, 2)}H`, 10 | `A:${NumberUtil.HEX(reg.A, 2)}H`, 11 | `F:${NumberUtil.HEX(reg.F, 2)}H`, 12 | `PC:${NumberUtil.HEX(reg.PC, 4)}H`, 13 | `SP:${NumberUtil.HEX(reg.SP, 4)}H`, 14 | `IX:${NumberUtil.HEX(reg.IX, 4)}H`, 15 | `IY:${NumberUtil.HEX(reg.IY, 4)}H`, 16 | `R:${NumberUtil.HEX(reg.R, 2)}H`, 17 | `I:${NumberUtil.HEX(reg.I, 2)}H`, 18 | ].join(" ")); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /bin/cli-command/run.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | module.exports = new CliCommand("run", function(mz700/*, args*/) { 4 | mz700.start(); 5 | }); 6 | -------------------------------------------------------------------------------- /bin/cli-command/sendkey.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | var MZ700KeyMatrix = require("../../js/MZ-700/mz700-key-matrix.js"); 4 | function CliCommandSendKey() { 5 | this._durationMake = 250; 6 | this._durationRelease = 250; 7 | } 8 | CliCommandSendKey.prototype = new CliCommand("key", function(mz700, args) { 9 | var inkey = []; 10 | var s = args.join(" ").toUpperCase(); 11 | var i = 0; 12 | while(i < s.length) { 13 | var c = s.charAt(i); 14 | if(c == '\\') { 15 | i++; 16 | if(i < s.length) { 17 | inkey.push(s.charAt(i)); 18 | } 19 | } else if(c == '[') { 20 | c = ""; 21 | i++; 22 | while(i < s.length && s.charAt(i) != ']') { 23 | c += s.charAt(i); 24 | i++; 25 | } 26 | if(i < s.length && c != "") { 27 | inkey.push(c); 28 | } 29 | i++; 30 | } else { 31 | inkey.push(s.charAt(i)); 32 | } 33 | i++; 34 | } 35 | const pushKeys = []; 36 | for(const strcode of inkey) { 37 | const key = MZ700KeyMatrix.Str2Key[strcode]; 38 | if(key) { 39 | pushKeys.push(key); 40 | } else { 41 | return Promise.reject(`Not found ${strcode}`); 42 | } 43 | } 44 | var durationRelease = this._durationRelese; 45 | var durationMake = this._durationMake; 46 | var pushKey = function(key) { 47 | return new Promise(function(resolv, reject) { 48 | try { 49 | mz700.setKeyState(key.strobe, key.bit, true, null); 50 | setTimeout(function() { 51 | mz700.setKeyState(key.strobe, key.bit, false, null); 52 | setTimeout(function() { resolv(); }, 53 | durationRelease); 54 | }, durationMake); 55 | } catch(ex) { 56 | reject(ex); 57 | } 58 | }); 59 | }; 60 | return new Promise(function(resolv, reject) { 61 | var sendKeys = function(i) { 62 | try { 63 | var key = pushKeys[i]; 64 | console.log("SEND KEY [" + i + "/" + pushKeys.length + "] " + key.face); 65 | pushKey(key).then(function() { 66 | if(++i < pushKeys.length) { 67 | sendKeys(i); 68 | } else { 69 | resolv(); 70 | } 71 | }).catch(function(err) { 72 | console.log(err); 73 | }); 74 | } catch(err) { 75 | reject(err); 76 | } 77 | }; 78 | sendKeys(0); 79 | }); 80 | }); 81 | CliCommandSendKey.prototype.setMakeReleaseDurations = function( 82 | durationMake, durationRelease) 83 | { 84 | this._durationMake = durationMake; 85 | this._durationRelease = durationRelease; 86 | }; 87 | module.exports = new CliCommandSendKey(); 88 | -------------------------------------------------------------------------------- /bin/cli-command/step.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | module.exports = new CliCommand("step", function(mz700, args) { 4 | var count = 1; 5 | if(args.length == 1) { 6 | count = parseInt(args[0]); 7 | } 8 | mz700.exec(count); 9 | }); 10 | -------------------------------------------------------------------------------- /bin/cli-command/stop.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var CliCommand = require("./command"); 3 | module.exports = new CliCommand("stop", function(mz700/*, args*/) { 4 | mz700.stop(); 5 | }); 6 | -------------------------------------------------------------------------------- /bin/lib/get-package-json.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fs = require("fs"); 3 | module.exports = function(moduleRootPath) { 4 | try { 5 | const fname = (moduleRootPath||"..") + "/package.json"; 6 | return JSON.parse(fs.readFileSync(fname, "utf-8")); 7 | } catch (ex) { 8 | console.log("get-package-json: ERROR " + ex); 9 | throw ex; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /bin/lib/mz-files.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fs = require("fs").promises; 3 | const path = require("path"); 4 | const {HEX} = require("../../js/lib/number-util.js"); 5 | const mztReadFile = require("../cli-command/mzt-read-file.js"); 6 | 7 | /** 8 | * Read NEWMON7.ROM 9 | * @returns {UintA8Array} A NEWMON7 binary 10 | */ 11 | async function readMzNewmon7Rom() { 12 | const pathname = path.join( 13 | __dirname, "../../mz_newmon/ROMS/NEWMON7.ROM"); 14 | const buffer = await fs.readFile(pathname); 15 | return Uint8Array.from(buffer); 16 | } 17 | 18 | async function loadMzt(filename) { 19 | const mztList = await mztReadFile(filename); 20 | if(mztList != null && mztList.length > 0) { 21 | mztList.forEach((mzt, i) => { 22 | const {addrLoad, fileSize, addrExec, filename} = mzt.header; 23 | console.log([ 24 | `[${i + 1}/${mztList.length}]`, 25 | `${HEX(addrLoad, 4)}h ---`, 26 | `${HEX((addrLoad + fileSize - 1), 4)}h`, 27 | `(${fileSize} bytes),`, 28 | `${HEX(addrExec, 4)}h, ${filename}`, 29 | ].join(" ")); 30 | }); 31 | } 32 | return mztList; 33 | } 34 | 35 | function mzt2cmt(mztList) { 36 | const mzt = mztList.shift(); 37 | return mzt.header.buffer.concat(mzt.body.buffer); 38 | } 39 | 40 | async function loadCmt(filename) { 41 | const mztToSetCmt = await loadMzt(filename); 42 | const cassetteTape = mzt2cmt(mztToSetCmt); 43 | return cassetteTape; 44 | } 45 | 46 | function writeMzt(mz700, mztList) { 47 | mztList.forEach(mzt => { 48 | const {addrLoad, fileSize} = mzt.header; 49 | const {buffer} = mzt.body; 50 | for(let i = 0; i < fileSize; i++) { 51 | mz700.memory.poke(addrLoad + i, buffer[i]); 52 | } 53 | }); 54 | } 55 | 56 | module.exports = { 57 | readMzNewmon7Rom, 58 | loadMzt, 59 | loadCmt, 60 | writeMzt, 61 | }; 62 | -------------------------------------------------------------------------------- /bin/lib/start-web-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require("path"); 3 | const childProcess = require("child_process"); 4 | 5 | /** 6 | * Raise up a http-server and open specific web-page. 7 | * @param {string} basePath Relative path from this npm root 8 | * @param {number} port port number to be listened 9 | * @param {string} url URL to show first 10 | * @returns {undefined} 11 | */ 12 | function startWebServer(basePath, port, url) { 13 | const opt = { cwd: path.join(__dirname, "../..") }; 14 | const command = `npm run http-server -- ${basePath} -p ${port} -o ${url}`; 15 | childProcess.exec(command, opt, (error, stdout, stderr) => { 16 | if (error) { 17 | console.error(`exec error: ${error}`); 18 | return; 19 | } 20 | console.log(`stdout: ${stdout}`); 21 | console.log(`stderr: ${stderr}`); 22 | }); 23 | if (process.platform === 'win32') { 24 | require('readline').createInterface({ 25 | input: process.stdin, 26 | output: process.stdout 27 | }).on('SIGINT', function () { 28 | process.emit('SIGINT'); 29 | }); 30 | } 31 | } 32 | module.exports = startWebServer; 33 | -------------------------------------------------------------------------------- /bin/mz700-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const path = require("path"); 4 | const Getopt = require('node-getopt'); 5 | const HashArg = require("hash-arg"); 6 | const readline = require("linebyline")(process.stdin); 7 | 8 | const MZ700 = require("../js/MZ-700/mz700.js"); 9 | const PCG700 = require("../js/lib/PCG-700"); 10 | const getPackageJson = require("./lib/get-package-json"); 11 | const { 12 | readMzNewmon7Rom, 13 | loadMzt, 14 | loadCmt, 15 | writeMzt, 16 | } = require("./lib/mz-files"); 17 | 18 | const CliCommand = require("./cli-command/command.js"); 19 | const cliCommandSendKey = require("./cli-command/sendkey.js"); 20 | const cliCommandVram = require("./cli-command/vram.js"); 21 | const commands = new CliCommand(); 22 | commands.install([ 23 | require("./cli-command/exit.js"), 24 | require("./cli-command/register.js"), 25 | require("./cli-command/run.js"), 26 | require("./cli-command/stop.js"), 27 | require("./cli-command/step.js"), 28 | require("./cli-command/jump.js"), 29 | require("./cli-command/breakpoint.js"), 30 | require("./cli-command/mem.js"), 31 | cliCommandSendKey, 32 | cliCommandVram, 33 | require("./cli-command/cmt.js"), 34 | require("./cli-command/conf.js") 35 | ]); 36 | cliCommandSendKey.setMakeReleaseDurations(200,50); 37 | 38 | const getopt = new Getopt([ 39 | ['c', 'set-cmt=FILENAME', 'set MZT file as cassette magnetic tape'], 40 | ['h', 'help', 'display this help'], 41 | ['v', 'version', 'show version'] 42 | ]); 43 | const npm = getPackageJson(path.join(__dirname, "..")); 44 | const description = "The Cli-Version MZ-700 Emulator. -- " + npm.name + "@" + npm.version; 45 | getopt.setHelp( 46 | "Usage: mz700-cli [OPTION] [MZT-filename]\n" + 47 | description + "\n" + 48 | "\n" + 49 | "[[OPTIONS]]\n" + 50 | "\n" + 51 | "Installation: npm install -g mz700-js\n" + 52 | "Repository: https://github.com/takamin/mz700-js"); 53 | 54 | const cli = getopt.parseSystem(); 55 | if(cli.options.help) { 56 | getopt.showHelp(); 57 | return; 58 | } 59 | if(cli.options.version) { 60 | console.log(description); 61 | return; 62 | } 63 | console.log(description); 64 | 65 | const argv = HashArg.get(["input_filename"], cli.argv); 66 | 67 | MZ700.prototype.subscribe = function(notify, handler) { 68 | console.log(`subscribe ${notify} = ${handler}`); 69 | }; 70 | 71 | function createMZ700() { 72 | const mz700 = new MZ700(); 73 | mz700.create({ 74 | "started": ()=> { /* empty */ }, 75 | "stopped": ()=> { /* empty */ }, 76 | "onBreak" : ()=> { /* empty */ }, 77 | "onVramUpdate": (index, dispcode, attr)=>{ 78 | cliCommandVram.setAt(index, dispcode, attr); 79 | }, 80 | 'startSound': ()=> { /* empty */ }, 81 | 'stopSound': ()=> { /* empty */ }, 82 | "onStartDataRecorder": ()=>{ /* empty */ }, 83 | "onStopDataRecorder": ()=>{ /* empty */ } 84 | }); 85 | return mz700; 86 | } 87 | 88 | async function main() { 89 | const fnCmt = cli.options["set-cmt"]; 90 | const fnLoad = argv.input_filename; 91 | const [newmon7, cassetteTape, mztList] = await Promise.all([ 92 | readMzNewmon7Rom(), 93 | fnCmt ? loadCmt(fnCmt) : null, 94 | fnLoad ? loadMzt(fnLoad) : null, 95 | ]); 96 | 97 | const mz700 = createMZ700(); 98 | const pcg700 = new PCG700(); 99 | mz700.attachPCG700(pcg700); 100 | 101 | mz700.setMonitorRom(newmon7); 102 | if(cassetteTape) { 103 | mz700.setCassetteTape(cassetteTape); 104 | } 105 | if(mztList) { 106 | writeMzt(mz700, mztList); 107 | } 108 | 109 | readline.on("line", line => { 110 | commands.executeCommandline(line, mz700, line); 111 | }); 112 | commands.runCli(); 113 | } 114 | main().catch(err => console.error(`Error: ${err.stack}`)); 115 | -------------------------------------------------------------------------------- /bin/mz700-web.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const startWebServer = require("./lib/start-web-server.js"); 4 | startWebServer("..", 3000, "mz700-js/emu.html"); 5 | -------------------------------------------------------------------------------- /bin/mz700-ws.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* tslint:disable:no-console */ 3 | "use strict"; 4 | const path = require("path"); 5 | const http = require("http"); 6 | const Getopt = require('node-getopt'); 7 | const HashArg = require("hash-arg"); 8 | const {WebSocketServer} = require("transworker"); 9 | const {createCanvas} = require("canvas"); 10 | 11 | const MZ700 = require("../js/MZ-700/mz700.js"); 12 | const MZ700CanvasRenderer = require('../js/lib/mz700-canvas-renderer.js'); 13 | const MZ700CG = require("../js/lib/mz700-cg.js"); 14 | const PCG700 = require("../js/lib/PCG-700.js"); 15 | const getPackageJson = require("./lib/get-package-json.js"); 16 | const startWebServer = require("./lib/start-web-server.js"); 17 | const { 18 | readMzNewmon7Rom, 19 | loadMzt, 20 | loadCmt, 21 | writeMzt, 22 | } = require("./lib/mz-files"); 23 | 24 | const getopt = new Getopt([ 25 | ['c', 'set-cmt=FILENAME', 'set MZT file as cassette magnetic tape'], 26 | ['h', 'help', 'display this help'], 27 | ['v', 'version', 'show version'] 28 | ]); 29 | const npm = getPackageJson(path.join(__dirname, "..")); 30 | const description = 31 | `The WebSocket-Version MZ-700 Emulator. -- ${npm.name}@${npm.version}`; 32 | getopt.setHelp([ 33 | "Usage: mz700-cli [OPTION] [MZT-filename]", 34 | description, 35 | "", 36 | "[[OPTIONS]]", 37 | "", 38 | "Installation: npm install -g mz700-js", 39 | "Repository: https://github.com/takamin/mz700-js", 40 | ].join("\n")); 41 | 42 | const cli = getopt.parseSystem(); 43 | if(cli.options.help) { 44 | getopt.showHelp(); 45 | return; 46 | } 47 | if(cli.options.version) { 48 | console.log(description); 49 | return; 50 | } 51 | console.log(description); 52 | 53 | const argv = HashArg.get(["input_filename"], cli.argv); 54 | 55 | function createCanvasRenderer() { 56 | const canvas = createCanvas(320, 200); 57 | const mz700CanvasRenderer = new MZ700CanvasRenderer(); 58 | mz700CanvasRenderer.create({ 59 | canvas: canvas, 60 | CG: new MZ700CG(MZ700CG.ROM, 8, 8), 61 | }); 62 | return mz700CanvasRenderer; 63 | } 64 | 65 | function createMZ700(transworker, mz700CanvasRenderer) { 66 | const mz700 = new MZ700(); 67 | let vramUpdated = true; 68 | const onVramUpdate = () => { 69 | vramUpdated = true; 70 | }; 71 | mz700.create({ 72 | started: () => transworker.postNotify("start"), 73 | stopped: () => transworker.postNotify("stop"), 74 | onBreak: () => transworker.postNotify("onBreak"), 75 | onVramUpdate: (index, dispcode, attr) => { 76 | mz700CanvasRenderer.writeVram(index, attr, dispcode); 77 | onVramUpdate(); 78 | }, 79 | startSound: freq => transworker.postNotify("startSound", [ freq ]), 80 | stopSound: () => transworker.postNotify("stopSound"), 81 | onStartDataRecorder: () => transworker.postNotify("onStartDataRecorder"), 82 | onStopDataRecorder: () => transworker.postNotify("onStopDataRecorder"), 83 | }); 84 | 85 | setInterval(()=>{ 86 | if(vramUpdated) { 87 | const imageData = mz700CanvasRenderer.getImageData(); 88 | const buffer = Buffer.from(imageData.data).toString("base64"); 89 | transworker.postNotify("onUpdateScrn", buffer); 90 | vramUpdated = false; 91 | } 92 | }, 1000/24); 93 | return mz700; 94 | } 95 | 96 | async function main() { 97 | const fnCmt = cli.options["set-cmt"]; 98 | const fnLoad = argv.input_filename; 99 | const [newmon7, cassetteTape, mztList] = await Promise.all([ 100 | readMzNewmon7Rom(), 101 | fnCmt ? await loadCmt(fnCmt) : null, 102 | fnLoad ? await loadMzt(fnLoad) : null, 103 | ]); 104 | const server = http.createServer(); 105 | server.listen(5000, "localhost"); 106 | WebSocketServer.listen(server, transworker=>{ 107 | const mz700CanvasRenderer = createCanvasRenderer(); 108 | mz700CanvasRenderer.setupRendering(); 109 | const mz700 = createMZ700(transworker, mz700CanvasRenderer); 110 | const pcg700 = new PCG700(mz700CanvasRenderer); 111 | mz700.attachPCG700(pcg700); 112 | 113 | mz700.setMonitorRom(newmon7); 114 | if(cassetteTape) { 115 | mz700.setCassetteTape(cassetteTape); 116 | } 117 | if(mztList) { 118 | writeMzt(mz700, mztList); 119 | } 120 | return mz700; 121 | }); 122 | startWebServer("..", 3000, "mz700-js/emu-ws.html"); 123 | } 124 | main().catch(err => console.error(`Error: ${err.stack}`)); 125 | -------------------------------------------------------------------------------- /bin/mzasm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const NumberUtil = require("../js/lib/number-util.js"); 3 | var Z80_assemble = require('../js/Z80/assembler'); 4 | var MZ_TapeHeader = require('../js/lib/mz-tape-header'); 5 | var changeExt = require('../js/lib/change-ext.js'); 6 | var fs = require('fs'); 7 | var getPackageJson = require("./lib/get-package-json"); 8 | var npmInfo = getPackageJson(__dirname + "/.."); 9 | var Getopt = require('node-getopt'); 10 | var getopt = new Getopt([ 11 | ['m', 'map=FILENAME', 'output map file name'], 12 | ['z', 'output-MZT-header', 'output MZT header'], 13 | ['a', 'loading-address=ADDR', 'set loading address'], 14 | ['e', 'execution-address=ADDR', 'set execution address'], 15 | ['t', 'reuse-mzt-header=FILENAME', "reuse the MZT header."], 16 | ['o', 'output-file=FILENAME', 'filename to output'], 17 | ['h', 'help', 'display this help'], 18 | ['v', 'version', 'show version'] 19 | ]); 20 | var cli = getopt.parseSystem(); 21 | var description = "A simple Z80 assembler -- " + npmInfo.name + "@" + npmInfo.version; 22 | getopt.setHelp( 23 | "Usage: mzasm [OPTION] filename [filename ...]\n" + 24 | description + "\n" + 25 | "\n" + 26 | "[[OPTIONS]]\n" + 27 | "\n" + 28 | "Installation: npm install -g mz700-js\n" + 29 | "Repository: https://github.com/takamin/mz700-js"); 30 | 31 | if(cli.options.help) { 32 | getopt.showHelp(); 33 | process.exit(0); 34 | } 35 | 36 | if(cli.options.version) { 37 | console.log(description); 38 | process.exit(0); 39 | } 40 | 41 | var args = require("hash-arg").get(["input_filenames:string[]"], cli.argv); 42 | if(cli.argv.length < 1) { 43 | console.error('error: no input file'); 44 | process.exit(-1); 45 | } 46 | 47 | // Determine the output filename 48 | var output_filename = null; 49 | if('output-file' in cli.options) { 50 | output_filename = cli.options['output-file']; 51 | } else { 52 | var ext = null; 53 | if('reuse-mzt-header' in cli.options 54 | || 'output-MZT-header' in cli.options) 55 | { 56 | ext = ".mzt"; 57 | } else { 58 | ext = ".bin"; 59 | } 60 | let input_filename = args.input_filenames[0]; 61 | output_filename = changeExt(input_filename, ext); 62 | } 63 | 64 | // Determine filename of address map 65 | var fnMap = null; 66 | if('map' in cli.options) { 67 | fnMap = cli.options['map']; 68 | } else { 69 | fnMap = changeExt(output_filename, ".map"); 70 | } 71 | 72 | // 73 | // MZT-Header 74 | // 75 | var mzt_header = null; 76 | if('reuse-mzt-header' in cli.options) { 77 | 78 | // 79 | // Load MZT-Header from other MZT-File. 80 | // 81 | var mzt_filebuf = fs.readFileSync(cli.options['reuse-mzt-header']); 82 | mzt_header = new MZ_TapeHeader(mzt_filebuf, 0); 83 | 84 | } else if('output-MZT-header' in cli.options) { 85 | 86 | // 87 | // Create MZT-Header from the informations specified in command line options 88 | // 89 | var load_addr = 0; 90 | var exec_addr = 0; 91 | if('loading-address' in cli.options) { 92 | load_addr = parseInt(cli.options['loading-address'], 0); 93 | exec_addr = load_addr; 94 | } 95 | if('execution-address' in cli.options) { 96 | exec_addr = parseInt(cli.options['execution-address'], 0); 97 | } 98 | mzt_header = MZ_TapeHeader.createNew(); 99 | mzt_header.setFilename(output_filename); 100 | mzt_header.setAddrLoad(load_addr); 101 | mzt_header.setAddrExec(exec_addr); 102 | } 103 | (async function() { 104 | let sources = []; 105 | await Promise.all(args.input_filenames.map( input_filename => { 106 | return new Promise( (resolve, reject) => { 107 | fs.readFile(input_filename, 'utf-8', function(err, data) { 108 | if(err) { 109 | reject(err); 110 | } else { 111 | sources.push(data); 112 | resolve(data); 113 | } 114 | }); 115 | }); 116 | })); 117 | 118 | // 119 | // Assemble 120 | // 121 | let asm = Z80_assemble.assemble(sources); 122 | 123 | // 124 | // Set binary size to MZT Header 125 | // 126 | var mzt_header_buf = []; 127 | if(mzt_header != null) { 128 | if(mzt_header.addrLoad == 0) { 129 | mzt_header.setAddrLoad(asm.minAddr); 130 | } 131 | if(mzt_header.addrExec == 0) { 132 | mzt_header.setAddrExec(asm.minAddr); 133 | } 134 | mzt_header.setFilesize(asm.buffer.length); 135 | mzt_header_buf = mzt_header.buffer; 136 | } 137 | 138 | // 139 | // Write out 140 | // 141 | fs.writeFileSync( 142 | output_filename, 143 | Buffer.from(mzt_header_buf.concat(asm.buffer))); 144 | 145 | // 146 | // Output address map 147 | // 148 | let map = Z80_assemble.hashMapArray(asm.label2value).map(function(item) { 149 | return [item.label, ":\t", NumberUtil.HEX(item.address, 4), "H"].join(''); 150 | }).join("\n"); 151 | if(map.length > 0) { 152 | fs.writeFileSync(fnMap, map); 153 | } 154 | }()); 155 | -------------------------------------------------------------------------------- /bin/mzdasm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const NumberUtil = require("../js/lib/number-util.js"); 3 | var Z80 = require("../js/Z80/Z80.js"); 4 | var Z80BinUtil = require("../js/Z80/bin-util.js"); 5 | const parseAddress = require("../js/lib/parse-addr.js"); 6 | const MZ_Tape = require("../js/lib/mz-tape.js"); 7 | var changeExt = require("../js/lib/change-ext.js"); 8 | var fs = require('fs'); 9 | var path = require("path"); 10 | var getPackageJson = require("./lib/get-package-json"); 11 | var npmInfo = getPackageJson(__dirname + "/.."); 12 | var Getopt = require('node-getopt'); 13 | var getopt = new Getopt([ 14 | ['m', 'map=FILENAME', 'map file to resolve addresses'], 15 | ['t', 'input-mzt', 'input file is mz-tape file'], 16 | ['o', 'output-file=FILENAME', 'filename to output'], 17 | ['l', 'load-to=ADDR', 'address to load'], 18 | ['c', 'to-console', 'print result to console'], 19 | ['h', 'help', 'display this help'], 20 | ['v', 'version', 'show version'] 21 | ]) 22 | var cli = getopt.parseSystem(); 23 | var description = "A simple Z80 dis-assembler. -- " + npmInfo.name + "@" + npmInfo.version; 24 | getopt.setHelp( 25 | "Usage: mzdas [OPTION] filename\n" + 26 | description + "\n" + 27 | "\n" + 28 | "[[OPTIONS]]\n" + 29 | "\n" + 30 | "mz700-js\n" + 31 | "Installation: npm install -g mz700-js\n" + 32 | "Repository: https://github.com/takamin/mz700-js"); 33 | 34 | if(cli.options.help) { 35 | getopt.showHelp(); 36 | process.exit(0); 37 | } 38 | 39 | if(cli.options.version) { 40 | console.log(description); 41 | process.exit(0); 42 | } 43 | 44 | var args = require("hash-arg").get(["input_filename"], cli.argv); 45 | if(cli.argv.length < 1) { 46 | console.error('error: no input file'); 47 | process.exit(-1); 48 | } 49 | 50 | var input_filename = args.input_filename; 51 | var input_mzt = cli.options['input-mzt'] || 52 | path.extname(input_filename).toLowerCase() == ".mzt"; 53 | var output_filename = cli.options['output-file'] || 54 | changeExt(input_filename, ".asm"); 55 | var addrLoad = (function(addr_tok) { 56 | if(addr_tok) { 57 | var a = parseAddress.parseNumLiteralPair(addr_tok); 58 | if(typeof(a[0]) === 'number') { 59 | return Z80BinUtil.pair(a[1], a[0]); 60 | } 61 | } 62 | return 0; 63 | }(cli.options['load-to'])); 64 | fs.readFile(input_filename, function(err, data) { 65 | if(err) { 66 | throw err; 67 | } 68 | var outbuf = []; 69 | const buf = Buffer.from(data); 70 | var dasmlist = []; 71 | var i; 72 | if(input_mzt) { 73 | var mzts = MZ_Tape.parseMZT(buf); 74 | for(i = 0; i < mzts.length; i++) { 75 | var mzt = mzts[i]; 76 | outbuf.push(mzt.header.getHeadline()); 77 | dasmlist = Z80.dasm( 78 | mzt.body.buffer, 0, 79 | mzt.header.fileSize, 80 | mzt.header.addrLoad); 81 | } 82 | } else { 83 | outbuf.push( 84 | ";======================================================", 85 | "; filename : '" + input_filename + "'", 86 | "; loadaddr : " + NumberUtil.HEX(addrLoad, 4) + "H", 87 | "; filesize : " + buf.length + " bytes / " + 88 | NumberUtil.HEX(buf.length, 4) + "H bytes", 89 | ";======================================================" 90 | ); 91 | dasmlist = Z80.dasm(buf, 0, buf.length, addrLoad); 92 | } 93 | var dasmlines = Z80.dasmlines(dasmlist); 94 | for(i = 0; i < dasmlines.length; i++) { 95 | outbuf.push(dasmlines[i]); 96 | } 97 | if(cli.options['to-console']) { 98 | console.log( 99 | outbuf.join("\n") + "\n"); 100 | } else { 101 | fs.writeFileSync( 102 | output_filename, 103 | outbuf.join("\n") + "\n"); 104 | } 105 | }); 106 | -------------------------------------------------------------------------------- /emu-ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | MZ-700 FULL JAVASCRIPT EMULATOR 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |

MZ-700 FULL JAVASCRIPT EMULATOR

38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /emu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | MZ-700 FULL JAVASCRIPT EMULATOR 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |

MZ-700 FULL JAVASCRIPT EMULATOR

38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /image/btnFullscreen-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnFullscreen-off.png -------------------------------------------------------------------------------- /image/btnFullscreen-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnFullscreen-on.png -------------------------------------------------------------------------------- /image/btnKeyboard-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnKeyboard-off.png -------------------------------------------------------------------------------- /image/btnKeyboard-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnKeyboard-on.png -------------------------------------------------------------------------------- /image/btnReset-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnReset-off.png -------------------------------------------------------------------------------- /image/btnReset-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnReset-on.png -------------------------------------------------------------------------------- /image/btnRun-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnRun-off.png -------------------------------------------------------------------------------- /image/btnRun-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnRun-on.png -------------------------------------------------------------------------------- /image/btnStepIn-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnStepIn-disabled.png -------------------------------------------------------------------------------- /image/btnStepIn-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnStepIn-off.png -------------------------------------------------------------------------------- /image/btnStepIn-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnStepIn-on.png -------------------------------------------------------------------------------- /image/btnStop-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnStop-off.png -------------------------------------------------------------------------------- /image/btnStop-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/btnStop-on.png -------------------------------------------------------------------------------- /image/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/favicon-16x16.png -------------------------------------------------------------------------------- /image/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/favicon-32x32.png -------------------------------------------------------------------------------- /image/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/favicon-48x48.png -------------------------------------------------------------------------------- /image/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/favicon-96x96.png -------------------------------------------------------------------------------- /image/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/favicon.png -------------------------------------------------------------------------------- /image/gbtnReset-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/gbtnReset-on.png -------------------------------------------------------------------------------- /image/icon-sound-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 53 | 58 | 60 | 65 | 66 | 79 | -------------------------------------------------------------------------------- /image/icon-sound-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 47 | 52 | 54 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /image/icon-sound.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /image/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takamin/mz700-js/13bb4e6276c28030a8bf2c744fad24fd7868ac63/image/title.png -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.css 3 | *.map 4 | -------------------------------------------------------------------------------- /lib/change-ext.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import path = require("path"); 3 | export default function changeExt(filename:string, ext:string):string { 4 | if(!/^\./.test(ext)) { 5 | ext = '.' + ext; 6 | } 7 | const oldExt:string = path.extname(filename); 8 | const re = new RegExp(`${oldExt}$`); 9 | return filename.replace(re, ext); 10 | } 11 | module.exports = changeExt; 12 | -------------------------------------------------------------------------------- /lib/do-later.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* tslint:disable:no-console */ 4 | /** 5 | * Run the job after only once even if invoked frequentry short intervals. 6 | * 7 | * Even if this is called frequently many times at short intervals, 8 | * this invoke only last one once. 9 | * 10 | * This is useful to run heavy process in a scroll or mouse event handler. 11 | * 12 | * @param {Function} job the process to invoked once 13 | * @param {number} duration duration in milliseconds. 14 | * @returns {undefined} 15 | * 16 | * @author K.Takami 17 | * @version 2012-01-24 18 | */ 19 | function doLater(job, duration) { 20 | if(job in doLater.TID) { 21 | clearTimeout(doLater.TID[job]); 22 | } 23 | doLater.TID[job] = setTimeout(() => { 24 | delete doLater.TID[job]; 25 | try { 26 | job.call(); 27 | } catch(e) { 28 | console.error("doLater fail on " + job + "\n" + e.stack); 29 | } 30 | }, duration); 31 | } 32 | 33 | // Timer ID by function. 34 | doLater.TID = {}; 35 | 36 | module.exports = doLater; 37 | -------------------------------------------------------------------------------- /lib/event-dispatcher.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * A base class which dispatches events. 5 | */ 6 | export default class EventDispatcher { 7 | 8 | _handlers = {}; 9 | 10 | /** 11 | * @constructor 12 | */ 13 | constructor() { 14 | this._handlers = {}; 15 | } 16 | 17 | /** 18 | * @param {string} eventName An event name. 19 | * @returns {undefined} 20 | */ 21 | declareEvent(eventName:string):void { 22 | this._handlers[eventName] = []; 23 | } 24 | 25 | /** 26 | * @param {string} eventName An event name 27 | * @param {Function} handler An event handler 28 | * @returns {undefined} 29 | */ 30 | addEventListener(eventName:string, handler:()=>void):void { 31 | this._handlers[eventName].push(handler); 32 | } 33 | 34 | /** 35 | * @param {string} eventName event name 36 | * @returns {undefined} 37 | */ 38 | fireEvent(eventName:string):void { 39 | this._handlers[eventName].forEach(handler => handler()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/flip-flop-counter.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import EventDispatcher from "./event-dispatcher"; 3 | 4 | /** 5 | * FlipFlopCounter 6 | */ 7 | export default class FlipFlopCounter extends EventDispatcher { 8 | _out:boolean; 9 | _counter:number; 10 | _counterMax:number; 11 | 12 | constructor(count:number) { 13 | super(); 14 | this.declareEvent("change"); 15 | 16 | this.initialize(); 17 | this._counterMax = count; 18 | } 19 | 20 | initialize():void { 21 | this._out = false; 22 | this._counter = 0; 23 | } 24 | 25 | readOutput():boolean { 26 | return this._out; 27 | } 28 | 29 | count():boolean { 30 | this._counter++; 31 | if(this._counter >= this._counterMax / 2) { 32 | this._out = !this._out; 33 | this._counter = 0; 34 | this.fireEvent("change"); 35 | return true; 36 | } 37 | return false; 38 | } 39 | } 40 | module.exports = FlipFlopCounter; 41 | -------------------------------------------------------------------------------- /lib/ic556.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import FlipFlopCounter from "../lib/flip-flop-counter"; 3 | 4 | // 5 | // IC BJ 556 6 | // 7 | export default class IC556 extends FlipFlopCounter { 8 | _reset: boolean; 9 | constructor(freq:number) { 10 | super(freq); 11 | this._reset = false; 12 | } 13 | 14 | count():boolean { 15 | if(this._reset) { 16 | return FlipFlopCounter.prototype.count.call(this); 17 | } 18 | return false; 19 | } 20 | 21 | loadReset(value:boolean):void { 22 | if(!value) { 23 | if(this._reset) { 24 | this._reset = false; 25 | this.initialize(); 26 | } 27 | } else { 28 | if(!this._reset) { 29 | this._reset = true; 30 | } 31 | } 32 | } 33 | } 34 | module.exports = IC556; 35 | -------------------------------------------------------------------------------- /lib/jquery-plugin/jquery.Z80-addr-spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const NumberUtil = require("../number-util.js"); 3 | const parseAddress = require("../parse-addr.js"); 4 | const jquery_plugin_class = require("./jquery_plugin_class"); 5 | jquery_plugin_class("Z80AddressSpecifier"); 6 | 7 | /** 8 | * Z80 address specification controll panel(jquery plugin) 9 | * @constructor 10 | * 11 | * @param {HTMLElement} element 12 | * The DOM element to create widget. 13 | * 14 | * @param {object|undefined} opt 15 | * An option for this widget. 16 | */ 17 | function Z80AddressSpecifier(element, opt) { 18 | this._element = element; 19 | this._opt = {}; 20 | if(opt) { 21 | this.create(opt); 22 | } 23 | } 24 | 25 | window.Z80AddressSpecifier = Z80AddressSpecifier; 26 | module.exports = Z80AddressSpecifier; 27 | 28 | /** 29 | * Create this widget. 30 | * 31 | * @param {object|undefined} opt 32 | * An option for this widget. 33 | * 34 | * @return {undefined} 35 | */ 36 | Z80AddressSpecifier.prototype.create = function(opt) { 37 | 38 | let $root = $(this._element).css("width", "550px"); 39 | 40 | const IDC = "z80-addr-spec"; 41 | if($root.hasClass(IDC)) { 42 | return; 43 | } 44 | $root.addClass(IDC); 45 | 46 | opt = opt || {}; 47 | Object.keys(this._opt).forEach(key=>{ 48 | if(key in opt) { 49 | this._opt[key] = opt[key]; 50 | } 51 | }); 52 | 53 | const getReg = regName => { 54 | return new Promise( (resolve, reject) => { 55 | try { 56 | $root.trigger("queryregister", [ regName, value => { 57 | resolve(value); 58 | }]); 59 | } catch(err) { 60 | reject(err); 61 | } 62 | }); 63 | }; 64 | 65 | [ 66 | {"H":"B","L": "C"}, 67 | {"H":"D","L": "E"}, 68 | {"H":"H","L": "L"}, 69 | "PC", "SP", "IX", "IY" 70 | ].forEach(regs => { 71 | var pair = ((typeof(regs) === "string") ? false : true); 72 | var name16 = (pair ? regs.H + regs.L : regs); 73 | var getRegValue = (pair ? 74 | (regs => { 75 | let value = 0; 76 | return getReg(regs.H).then(value_H => { 77 | value = value_H * 256; 78 | return getReg(regs.L); 79 | }).then( value_L => { 80 | return value + value_L; 81 | }); 82 | }) : (regs => { return getReg(regs); })); 83 | $root 84 | .append($("