Merge branch 'newemu' for new-style emulators in src/machine
This commit is contained in:
commit
10f638bc4d
|
@ -5,4 +5,5 @@ local
|
|||
release
|
||||
gen
|
||||
test/output
|
||||
tests_output/
|
||||
.DS_Store
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
[submodule "jsnes"]
|
||||
path = jsnes
|
||||
url = https://github.com/sehugg/jsnes
|
||||
[submodule "bootstrap-tourist"]
|
||||
path = bootstrap-tourist
|
||||
url = https://github.com/IGreatlyDislikeJavascript/bootstrap-tourist
|
||||
[submodule "nanoasm"]
|
||||
path = nanoasm
|
||||
url = https://github.com/sehugg/nanoasm
|
||||
|
|
4
Makefile
4
Makefile
|
@ -23,12 +23,12 @@ lint:
|
|||
|
||||
web:
|
||||
ifconfig | grep inet
|
||||
python3 scripts/serveit.py 2>> http.out
|
||||
python3 scripts/serveit.py 2>> /dev/null #http.out
|
||||
|
||||
tsweb:
|
||||
ifconfig | grep inet
|
||||
$(TSC) -w &
|
||||
python3 scripts/serveit.py 2>> http.out
|
||||
python3 scripts/serveit.py 2>> /dev/null #http.out
|
||||
|
||||
astrolibre.b64.txt: astrolibre.rom
|
||||
lzg -9 $< | base64 -w 0 > $@
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 12ede284786daedce1b8abf0a848687977b88db5
|
15
css/ui.css
15
css/ui.css
|
@ -18,7 +18,7 @@
|
|||
display: inline-block;
|
||||
border-bottom: 1px dotted black;
|
||||
}
|
||||
.tooltipbox .tooltiptext {
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 500px;
|
||||
background-color: #660000;
|
||||
|
@ -48,6 +48,19 @@
|
|||
color:#ccccff;
|
||||
background-color:#000066;
|
||||
}
|
||||
.tooltiptrack {
|
||||
background-color: #666;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
border: 2px solid #999;
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
pointer-events: none;
|
||||
}
|
||||
#controls_top {
|
||||
position:absolute;
|
||||
padding:0.5em;
|
||||
|
|
|
@ -6,7 +6,10 @@ TODO:
|
|||
- confuse code/data in listing
|
||||
- show memory locations hovering over lines
|
||||
- don't check against ROM signatures
|
||||
- DASM: macro forward refs
|
||||
- DASM
|
||||
- macro forward refs
|
||||
- labels start with . omit timing info (isCode)
|
||||
- spaces in filename don't parse code listing (DASM, maybe more)
|
||||
- asm: support macro expansion
|
||||
- multiple breakpoints, expression breakpoints
|
||||
- watchpoints
|
||||
|
@ -31,7 +34,6 @@ TODO:
|
|||
- click to break on raster position
|
||||
- restructure src/ folders
|
||||
- debug bankswitching for funky formats
|
||||
- spaces in filename don't parse code listing (DASM, maybe more)
|
||||
- 'undefined' for bitmap replacer
|
||||
- requestInterrupt needs to be disabled after breakpoint?
|
||||
- C/asm source formatter
|
||||
|
@ -123,6 +125,7 @@ TODO:
|
|||
- separate Scope View
|
||||
- single-stepping vector games makes screen fade
|
||||
- break on stack overflow, illegal op, bad access, BRK, etc
|
||||
- show in scope instead?
|
||||
- nes
|
||||
- replay doesn't work for nes (force background tile redraw)
|
||||
- nes debug view toolbar
|
||||
|
@ -179,6 +182,12 @@ TODO:
|
|||
- can't step back twice?
|
||||
- compiler bug in chase
|
||||
- "shared" in URL doesn't work, leave in URL? (also importURL)
|
||||
- segments disappear in memory map when binary unchanged
|
||||
|
||||
- TypeError: null is not an object (evaluating 'n.destination')
|
||||
https://8bitworkshop.com/v3.4.1/javatari.js/release/javatari/javatari.js
|
||||
(32:443651) Safari 12.1.2
|
||||
|
||||
|
||||
WEB WORKER FORMAT
|
||||
|
||||
|
@ -303,6 +312,23 @@ Assets come from:
|
|||
- structured data (palette, sprites, metasprites, levels, etc)
|
||||
- think about new comment format, platform-specific types
|
||||
|
||||
Programmatic Asset Language
|
||||
- load PNG, draw image, or generate bitmap font
|
||||
- split into images
|
||||
- paletteize
|
||||
- fit into tileset/spriteset
|
||||
- NES: 8x8 or 8x16 tiles
|
||||
- 7800: 256xN slice
|
||||
- VCS: 24xN playfield, 8xN sprite, 32xN big sprite
|
||||
- generate data file (asm/c/bin)
|
||||
- generate char mapping
|
||||
- tile generator (makechr-like)
|
||||
- metasprites too
|
||||
- RLE compression
|
||||
- check constraints
|
||||
|
||||
|
||||
---
|
||||
|
||||
Github Support
|
||||
|
||||
|
|
|
@ -85,6 +85,11 @@ function require(modname) {
|
|||
<script src="gen/recorder.js"></script>
|
||||
<script src="gen/embedui.js"></script>
|
||||
|
||||
<script src="gen/devices.js"></script>
|
||||
<script src="gen/cpu/MOS6502.js"></script>
|
||||
<script src="gen/cpu/ZilogZ80.js"></script>
|
||||
<script src="gen/machine/vdp_z80.js"></script>
|
||||
|
||||
<script src="lib/liblzg.js"></script>
|
||||
|
||||
<script>
|
||||
|
|
22
index.html
22
index.html
|
@ -150,12 +150,21 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="?platform=vcs">Atari 2600</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=nes">NES</a></li>
|
||||
<hr>
|
||||
<li><a class="dropdown-item" href="?platform=astrocade">Bally Astrocade</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=coleco">ColecoVision</a></li>
|
||||
<!--<li><a class="dropdown-item" href="?platform=sms-sg1000-libcv">Sega SG-1000</a></li>-->
|
||||
<li><a class="dropdown-item" href="?platform=sms-sms-libcv">Sega Master System</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=atari7800">Atari 7800</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown dropdown-submenu">
|
||||
<a tabindex="-1" href="#">Computers</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="?platform=apple2">Apple ][+</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=c64">Commodore 64</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=msx">MSX (BIOS)</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=msx-libcv">MSX (libCV)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown dropdown-submenu">
|
||||
|
@ -180,8 +189,8 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<a tabindex="-1" href="#">Other</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="?platform=vcs.mame">Atari 2600 (MAME)</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=nes.mame">NES (MAME)</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=vector-ataricolor">Atari Color Vector (6502)</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=nes.mame">NES (MAME)</a></li>
|
||||
<li><a class="dropdown-item" href="?platform=markdown">Markdown</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -252,6 +261,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<div id="workspace">
|
||||
</div>
|
||||
<div class="emulator disable-select" id="emulator">
|
||||
<!-- replay slider -->
|
||||
<div id="replaydiv" class="replaydiv" style="display:none">
|
||||
<div style="display:flex">
|
||||
<button id="replay_min" class="btn" title="Start of replay"><span class="glyphicon glyphicon-fast-backward" aria-hidden="true"></span></button>
|
||||
|
@ -475,8 +485,8 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
|
||||
<script src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="bootstrap/js/bootbox.all.min.js"></script>
|
||||
<link rel="stylesheet" href="bootstrap-tourist/bootstrap-tourist.css">
|
||||
<script src="bootstrap-tourist/bootstrap-tourist.js"></script>
|
||||
<link rel="stylesheet" href="lib/bootstrap-tourist.css">
|
||||
<script src="lib/bootstrap-tourist.js"></script>
|
||||
|
||||
<script src="src/codemirror/codemirror.js"></script>
|
||||
<script src="codemirror/mode/clike/clike.js"></script>
|
||||
|
@ -524,10 +534,10 @@ function require(modname) {
|
|||
<script src="tss/js/tss/AudioLooper.js"></script>
|
||||
<script src="tss/js/Log.js"></script>
|
||||
|
||||
<script src="gen/vlist.js"></script>
|
||||
<script src="gen/video/tms9918a.js"></script>
|
||||
<script src="gen/util.js"></script>
|
||||
<script src="gen/store.js"></script>
|
||||
<script src="src/vlist.js"></script>
|
||||
<script src="gen/emu.js"></script>
|
||||
<script src="gen/baseplatform.js"></script>
|
||||
<script src="gen/analysis.js"></script>
|
||||
|
@ -545,6 +555,10 @@ function require(modname) {
|
|||
<script src="gen/ui.js"></script>
|
||||
<!-- <script src="src/audio/votrax.js"></script> -->
|
||||
<!-- <script src="local/lzg.js"></script> -->
|
||||
<script src="gen/devices.js"></script>
|
||||
<script src="gen/cpu/MOS6502.js"></script>
|
||||
<script src="gen/cpu/ZilogZ80.js"></script>
|
||||
<script src="gen/machine/vdp_z80.js"></script>
|
||||
|
||||
<script>
|
||||
// submenus open on click + hover
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* ========================================================================
|
||||
* Bootstrap Tourist v0.7
|
||||
* Copyright FFS 2019
|
||||
* @ IGreatlyDislikeJavascript on Github
|
||||
*
|
||||
* bootstrap-tour - v0.11.0
|
||||
* http://bootstraptour.com
|
||||
* ========================================================================
|
||||
* Copyright 2012-2015 Ulrich Sossou
|
||||
*
|
||||
* ========================================================================
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================================
|
||||
*/
|
||||
|
||||
.tour-backdrop {
|
||||
position: absolute;
|
||||
z-index: 1100;
|
||||
background-color: #000;
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
.tour-prevent {
|
||||
position: absolute;
|
||||
z-index: 1102;
|
||||
background-color: #ccc;
|
||||
opacity: 0.20;
|
||||
filter: alpha(opacity=20);
|
||||
}
|
||||
.popover[class*="tour-"] {
|
||||
z-index: 1110;
|
||||
}
|
||||
.popover[class*="tour-"] .popover-navigation {
|
||||
padding: 9px 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="end"] {
|
||||
float: right;
|
||||
}
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="prev"],
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="next"],
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="end"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled,
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled,
|
||||
.popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled {
|
||||
cursor: default;
|
||||
}
|
||||
.popover[class*="tour-"].orphan {
|
||||
position: fixed;
|
||||
margin-top: 0;
|
||||
}
|
||||
.popover[class*="tour-"].orphan .arrow {
|
||||
display: none;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"src_folders" : ["test/web"],
|
||||
|
||||
"webdriver" : {
|
||||
"start_process": true,
|
||||
"server_path": "node_modules/.bin/chromedriver",
|
||||
"port": 9515
|
||||
},
|
||||
|
||||
"test_settings" : {
|
||||
"default" : {
|
||||
"desiredCapabilities": {
|
||||
"browserName": "chrome"
|
||||
},
|
||||
"screenshots" : {
|
||||
"enabled" : "true",
|
||||
"path" : "",
|
||||
"on_failure": true,
|
||||
"on_error": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
|
@ -8,23 +8,27 @@
|
|||
"url": "git+https://github.com/sehugg/8bitworkshop.git"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"bootstrap-tourist": "^0.2.1",
|
||||
"jquery": "^3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootbox": "^4.4.36",
|
||||
"@types/bootstrap": "^3.3.42",
|
||||
"@types/jquery": "^3.3.29",
|
||||
"atob": "^2.1.x",
|
||||
"btoa": "^1.2.x",
|
||||
"chromedriver": "^76.0.1",
|
||||
"clipboard": "^2.0.4",
|
||||
"jquery": "^3.4.1",
|
||||
"jsdom": "^12.2.0",
|
||||
"lzg": "^1.0.x",
|
||||
"mocha": "^5.2.x",
|
||||
"nightwatch": "^1.2.1",
|
||||
"octokat": "^0.10.0",
|
||||
"pngjs": "^3.3.3",
|
||||
"typescript": "^3.5.3",
|
||||
"typescript-formatter": "^7.2.2",
|
||||
"wavedrom-cli": "^0.5.x"
|
||||
"rgbquant": "^1.1.2",
|
||||
"typescript": "^3.6.2",
|
||||
"typescript-formatter": "^7.2.2"
|
||||
},
|
||||
"main": "main.js",
|
||||
"directories": {
|
||||
|
@ -36,6 +40,7 @@
|
|||
"test": "npm run test-node",
|
||||
"test-one": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000",
|
||||
"test-node": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli",
|
||||
"test-web": "NODE_PATH=$(pwd) nightwatch test/web",
|
||||
"test-worker": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testworker.js",
|
||||
"test-platforms": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 test/cli/testplatforms.js",
|
||||
"test-profile": "NODE_PATH=$(pwd) mocha --recursive --timeout 60000 --prof test/cli"
|
||||
|
|
|
@ -0,0 +1,833 @@
|
|||
/*
|
||||
*
|
||||
* The Abandoned Farm House Adventure
|
||||
*
|
||||
* Jeff Tranter <tranter@pobox.com>
|
||||
*
|
||||
* Written in standard C but designed to run on the Apple Replica 1
|
||||
* or Apple II using the CC65 6502 assembler.
|
||||
*
|
||||
* Copyright 2012-2015 Jeff Tranter
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Revision History:
|
||||
*
|
||||
* Version Date Comments
|
||||
* ------- ---- --------
|
||||
* 0.0 13 Mar 2012 First alpha version
|
||||
* 0.1 18 Mar 2012 First beta version
|
||||
* 0.9 19 Mar 2012 First public release
|
||||
* 1.0 06 Sep 2015 Lower case and other Apple II improvements.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef __CC65__
|
||||
#include <conio.h>
|
||||
#endif
|
||||
|
||||
/* CONSTANTS */
|
||||
|
||||
/* Maximum number of items user can carry */
|
||||
#define MAXITEMS 5
|
||||
|
||||
/* Number of locations */
|
||||
#define NUMLOCATIONS 32
|
||||
|
||||
/* TYPES */
|
||||
|
||||
/* To optimize for code size and speed, most numbers are 8-bit chars when compiling for CC65. */
|
||||
#ifdef __CC65__
|
||||
typedef char number;
|
||||
#else
|
||||
typedef int number;
|
||||
#endif
|
||||
|
||||
/* Directions */
|
||||
typedef enum {
|
||||
North,
|
||||
South,
|
||||
East,
|
||||
West,
|
||||
Up,
|
||||
Down
|
||||
} Direction_t;
|
||||
|
||||
/* Items */
|
||||
typedef enum {
|
||||
NoItem,
|
||||
Key,
|
||||
Pitchfork,
|
||||
Flashlight,
|
||||
Lamp,
|
||||
Oil,
|
||||
Candybar,
|
||||
Bottle,
|
||||
Doll,
|
||||
ToyCar,
|
||||
Matches,
|
||||
GoldCoin,
|
||||
SilverCoin,
|
||||
StaleMeat,
|
||||
Book,
|
||||
Cheese,
|
||||
OldRadio,
|
||||
LastItem=OldRadio
|
||||
} Item_t;
|
||||
|
||||
/* Locations */
|
||||
typedef enum {
|
||||
NoLocation,
|
||||
Driveway1,
|
||||
Driveway2,
|
||||
Driveway3,
|
||||
Driveway4,
|
||||
Driveway5,
|
||||
Garage,
|
||||
WorkRoom,
|
||||
Hayloft,
|
||||
Kitchen,
|
||||
DiningRoom,
|
||||
BottomStairs,
|
||||
DrawingRoom,
|
||||
Study,
|
||||
TopStairs,
|
||||
BoysBedroom,
|
||||
GirlsBedroom,
|
||||
MasterBedroom,
|
||||
ServantsQuarters,
|
||||
LaundryRoom,
|
||||
FurnaceRoom,
|
||||
VacantRoom,
|
||||
Cistern,
|
||||
Tunnel,
|
||||
Woods24,
|
||||
Woods25,
|
||||
Woods26,
|
||||
WolfTree,
|
||||
Woods28,
|
||||
Woods29,
|
||||
Woods30,
|
||||
Woods31,
|
||||
} Location_t;
|
||||
|
||||
/* TABLES */
|
||||
|
||||
/* Names of directions */
|
||||
char *DescriptionOfDirection[] = {
|
||||
"north", "south", "east", "west", "up", "down"
|
||||
};
|
||||
|
||||
/* Names of items */
|
||||
char *DescriptionOfItem[LastItem+1] = {
|
||||
"",
|
||||
"key",
|
||||
"pitchfork",
|
||||
"flashlight",
|
||||
"lamp",
|
||||
"oil",
|
||||
"candybar",
|
||||
"bottle",
|
||||
"doll",
|
||||
"toy car",
|
||||
"matches",
|
||||
"gold coin",
|
||||
"silver coin",
|
||||
"stale meat",
|
||||
"book",
|
||||
"cheese",
|
||||
"old radio",
|
||||
};
|
||||
|
||||
/* Names of locations */
|
||||
char *DescriptionOfLocation[NUMLOCATIONS] = {
|
||||
"",
|
||||
"in the driveway near your car",
|
||||
"in the driveway",
|
||||
"in front of the garage",
|
||||
"in front of the barn",
|
||||
"at the door to the house",
|
||||
"in the garage",
|
||||
"in the workroom of the barn",
|
||||
"in the hayloft of the barn",
|
||||
"in the kitchen",
|
||||
"in the dining room",
|
||||
"at the bottom of the stairs",
|
||||
"in the drawing room",
|
||||
"in the study",
|
||||
"at the top of the stairs",
|
||||
"in a boy's bedroom",
|
||||
"in a girl's bedroom",
|
||||
"in the master bedroom next to\na bookcase",
|
||||
"in the servant's quarters",
|
||||
"in the basement laundry room",
|
||||
"in the furnace room",
|
||||
"in a vacant room next to a\nlocked door",
|
||||
"in the cistern",
|
||||
"in an underground tunnel. There are rats here",
|
||||
"in the woods near a trapdoor",
|
||||
"in the woods",
|
||||
"in the woods",
|
||||
"in the woods next to a tree",
|
||||
"in the woods",
|
||||
"in the woods",
|
||||
"in the woods",
|
||||
"in the woods",
|
||||
};
|
||||
|
||||
/* DATA */
|
||||
|
||||
/* Inventory of what player is carrying */
|
||||
Item_t Inventory[MAXITEMS];
|
||||
|
||||
/* Location of each item. Index is the item number, returns the location. 0 if item is gone */
|
||||
Location_t locationOfItem[LastItem+1];
|
||||
|
||||
/* Map. Given a location and a direction to move, returns the location it connects to, or 0 if not a valid move. Map can change during game play. */
|
||||
Direction_t Move[NUMLOCATIONS][6] = {
|
||||
/* N S E W U D */
|
||||
{ 0, 0, 0, 0, 0, 0 }, /* 0 */
|
||||
{ 2, 0, 0, 0, 0, 0 }, /* 1 */
|
||||
{ 4, 1, 3, 5, 0, 0 }, /* 2 */
|
||||
{ 0, 0, 6, 2, 0, 0 }, /* 3 */
|
||||
{ 7, 2, 0, 0, 0, 0 }, /* 4 */
|
||||
{ 0, 0, 2, 9, 0, 0 }, /* 5 */
|
||||
{ 0, 0, 0, 3, 0, 0 }, /* 6 */
|
||||
{ 0, 4, 0, 0, 8, 0 }, /* 7 */
|
||||
{ 0, 0, 0, 0, 0, 7 }, /* 8 */
|
||||
{ 0,10, 5, 0, 0,19 }, /* 9 */
|
||||
{ 9, 0, 0,11, 0, 0 }, /* 10 */
|
||||
{ 0, 0,10,12,14, 0 }, /* 11 */
|
||||
{ 13, 0,11, 0, 0, 0 }, /* 12 */
|
||||
{ 0,12, 0, 0, 0, 0 }, /* 13 */
|
||||
{ 16, 0,15,17, 0,11 }, /* 14 */
|
||||
{ 0, 0, 0,14, 0, 0 }, /* 15 */
|
||||
{ 0,14, 0, 0, 0, 0 }, /* 16 */
|
||||
{ 0, 0,14, 0, 0, 0 }, /* 17 */
|
||||
{ 0, 0, 0, 0, 0,13 }, /* 18 */
|
||||
{ 0, 0, 0,20, 9, 0 }, /* 19 */
|
||||
{ 21, 0,19, 0, 0, 0 }, /* 20 */
|
||||
{ 0,20, 0,22, 0, 0 }, /* 21 */
|
||||
{ 0, 0,21, 0, 0, 0 }, /* 22 */
|
||||
{ 24,21, 0, 0, 0, 0 }, /* 23 */
|
||||
{ 29,23, 0,26, 0, 0 }, /* 24 */
|
||||
{ 26, 0,24, 0, 0, 0 }, /* 25 */
|
||||
{ 27,25,29, 0, 0, 0 }, /* 26 */
|
||||
{ 0,26,28, 0, 0, 0 }, /* 27 */
|
||||
{ 0,29,31,27, 0, 0 }, /* 28 */
|
||||
{ 28,24,30,26, 0, 0 }, /* 29 */
|
||||
{ 31, 0, 0,29, 0, 0 }, /* 30 */
|
||||
{ 0,30, 0,29, 0, 0 }, /* 31 */
|
||||
};
|
||||
|
||||
/* Current location */
|
||||
number currentLocation;
|
||||
|
||||
/* Number of turns played in game */
|
||||
int turnsPlayed;
|
||||
|
||||
/* True if player has lit the lamp. */
|
||||
number lampLit;
|
||||
|
||||
/* True if lamp filled with oil. */
|
||||
number lampFilled;
|
||||
|
||||
/* True if player ate food. */
|
||||
number ateFood;
|
||||
|
||||
/* True if player drank water. */
|
||||
number drankWater;
|
||||
|
||||
/* Incremented each turn you are in the tunnel. */
|
||||
number ratAttack;
|
||||
|
||||
/* Tracks state of wolf attack */
|
||||
number wolfState;
|
||||
|
||||
/* Set when game is over */
|
||||
number gameOver;
|
||||
|
||||
const char *introText = " Abandoned Farmhouse Adventure\n By Jeff Tranter\n\nYour three-year-old grandson has gone\nmissing and was last seen headed in the\ndirection of the abandoned family farm.\nIt's a dangerous place to play. You\nhave to find him before he gets hurt,\nand it will be getting dark soon...\n";
|
||||
|
||||
const char *helpString = "Valid commands:\ngo east/west/north/south/up/down \nlook\nuse <object>\nexamine <object>\ntake <object>\ndrop <object>\ninventory\nhelp\nquit\nYou can abbreviate commands and\ndirections to the first letter.\nType just the first letter of\na direction to move.\n";
|
||||
|
||||
/* Line of user input */
|
||||
char buffer[40];
|
||||
|
||||
/* Clear the screen */
|
||||
void clearScreen()
|
||||
{
|
||||
#if defined(__APPLE2__)
|
||||
clrscr();
|
||||
#else
|
||||
number i;
|
||||
for (i = 0; i < 24; ++i)
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Return 1 if carrying an item */
|
||||
number carryingItem(char *item)
|
||||
{
|
||||
number i;
|
||||
|
||||
for (i = 0; i < MAXITEMS; i++) {
|
||||
if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item)))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return 1 if item it at current location (not carried) */
|
||||
number itemIsHere(char *item)
|
||||
{
|
||||
number i;
|
||||
|
||||
/* Find number of the item. */
|
||||
for (i = 1; i <= LastItem; i++) {
|
||||
if (!strcasecmp(item, DescriptionOfItem[i])) {
|
||||
/* Found it, but is it here? */
|
||||
if (locationOfItem[i] == currentLocation) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Inventory command */
|
||||
void doInventory()
|
||||
{
|
||||
number i;
|
||||
int found = 0;
|
||||
|
||||
printf("%s", "You are carrying:\n");
|
||||
for (i = 0; i < MAXITEMS; i++) {
|
||||
if (Inventory[i] != 0) {
|
||||
printf(" %s\n", DescriptionOfItem[Inventory[i]]);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
printf(" nothing\n");
|
||||
}
|
||||
|
||||
/* Help command */
|
||||
void doHelp()
|
||||
{
|
||||
printf("%s", helpString);
|
||||
}
|
||||
|
||||
/* Look command */
|
||||
void doLook()
|
||||
{
|
||||
number i, loc, seen;
|
||||
|
||||
printf("You are %s.\n", DescriptionOfLocation[currentLocation]);
|
||||
|
||||
seen = 0;
|
||||
printf("You see:\n");
|
||||
for (i = 1; i <= LastItem; i++) {
|
||||
if (locationOfItem[i] == currentLocation) {
|
||||
printf(" %s\n", DescriptionOfItem[i]);
|
||||
seen = 1;
|
||||
}
|
||||
}
|
||||
if (!seen)
|
||||
printf(" nothing special\n");
|
||||
|
||||
printf("You can go:");
|
||||
|
||||
for (i = North; i <= Down; i++) {
|
||||
loc = Move[currentLocation][i];
|
||||
if (loc != 0) {
|
||||
printf(" %s", DescriptionOfDirection[i]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Quit command */
|
||||
void doQuit()
|
||||
{
|
||||
printf("%s", "Are you sure you want to quit (y/n)? ");
|
||||
fgets(buffer, sizeof(buffer)-1, stdin);
|
||||
if (tolower(buffer[0]) == 'y') {
|
||||
gameOver = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Drop command */
|
||||
void doDrop()
|
||||
{
|
||||
number i;
|
||||
char *sp;
|
||||
char *item;
|
||||
|
||||
/* Command line should be like "D[ROP] ITEM" Item name will be after after first space. */
|
||||
sp = strchr(buffer, ' ');
|
||||
if (sp == NULL) {
|
||||
printf("Drop what?\n");
|
||||
return;
|
||||
}
|
||||
|
||||
item = sp + 1;
|
||||
|
||||
/* See if we have this item */
|
||||
for (i = 0; i < MAXITEMS; i++) {
|
||||
if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item))) {
|
||||
/* We have it. Add to location. */
|
||||
locationOfItem[Inventory[i]] = currentLocation;
|
||||
/* And remove from inventory */
|
||||
Inventory[i] = 0;
|
||||
printf("Dropped %s.\n", item);
|
||||
++turnsPlayed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* If here, don't have it. */
|
||||
printf("Not carrying %s.\n", item);
|
||||
}
|
||||
|
||||
/* Take command */
|
||||
void doTake()
|
||||
{
|
||||
number i, j;
|
||||
char *sp;
|
||||
char *item;
|
||||
|
||||
/* Command line should be like "T[AKE] ITEM" Item name will be after after first space. */
|
||||
sp = strchr(buffer, ' ');
|
||||
if (sp == NULL) {
|
||||
printf("Take what?\n");
|
||||
return;
|
||||
}
|
||||
|
||||
item = sp + 1;
|
||||
|
||||
if (carryingItem(item)) {
|
||||
printf("Already carrying it.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find number of the item. */
|
||||
for (i = 1; i <= LastItem; i++) {
|
||||
if (!strcasecmp(item, DescriptionOfItem[i])) {
|
||||
/* Found it, but is it here? */
|
||||
if (locationOfItem[i] == currentLocation) {
|
||||
/* It is here. Add to inventory. */
|
||||
for (j = 0; j < MAXITEMS; j++) {
|
||||
if (Inventory[j] == 0) {
|
||||
Inventory[j] = i;
|
||||
/* And remove from location. */
|
||||
locationOfItem[i] = 0;
|
||||
printf("Took %s.\n", item);
|
||||
++turnsPlayed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reached maximum number of items to carry */
|
||||
printf("You can't carry any more. Drop something.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If here, don't see it. */
|
||||
printf("I see no %s here.\n", item);
|
||||
}
|
||||
|
||||
/* Go command */
|
||||
void doGo()
|
||||
{
|
||||
char *sp;
|
||||
char dirChar;
|
||||
Direction_t dir;
|
||||
|
||||
/* Command line should be like "G[O] N[ORTH]" Direction will be
|
||||
the first letter after a space. Or just a single letter
|
||||
direction N S E W U D or full directon NORTH etc. */
|
||||
|
||||
sp = strrchr(buffer, ' ');
|
||||
if (sp != NULL) {
|
||||
dirChar = *(sp+1);
|
||||
} else {
|
||||
dirChar = buffer[0];
|
||||
}
|
||||
dirChar = tolower(dirChar);
|
||||
|
||||
if (dirChar == 'n') {
|
||||
dir = North;
|
||||
} else if (dirChar == 's') {
|
||||
dir = South;
|
||||
} else if (dirChar == 'e') {
|
||||
dir = East;
|
||||
} else if (dirChar == 'w') {
|
||||
dir = West;
|
||||
} else if (dirChar == 'u') {
|
||||
dir = Up;
|
||||
} else if (dirChar == 'd') {
|
||||
dir = Down;
|
||||
} else {
|
||||
printf("Go where?\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Move[currentLocation][dir] == 0) {
|
||||
printf("You can't go %s from here.\n", DescriptionOfDirection[dir]);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We can move */
|
||||
currentLocation = Move[currentLocation][dir];
|
||||
printf("You are %s.\n", DescriptionOfLocation[currentLocation]);
|
||||
++turnsPlayed;
|
||||
}
|
||||
|
||||
/* Examine command */
|
||||
void doExamine()
|
||||
{
|
||||
char *sp;
|
||||
char *item;
|
||||
|
||||
/* Command line should be like "E[XAMINE] ITEM" Item name will be after after first space. */
|
||||
sp = strchr(buffer, ' ');
|
||||
if (sp == NULL) {
|
||||
printf("Examine what?\n");
|
||||
return;
|
||||
}
|
||||
|
||||
item = sp + 1;
|
||||
++turnsPlayed;
|
||||
|
||||
/* Examine bookcase - not an object */
|
||||
if (!strcasecmp(item, "bookcase")) {
|
||||
printf("You pull back a book and the bookcase\nopens up to reveal a secret room.\n");
|
||||
Move[17][North] = 18;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure item is being carried or is in the current location */
|
||||
if (!carryingItem(item) && !itemIsHere(item)) {
|
||||
printf("I don't see it here.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Examine Book */
|
||||
if (!strcasecmp(item, "book")) {
|
||||
printf("It is a very old book entitled\n\"Apple 1 operation manual\".\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Examine Flashlight */
|
||||
if (!strcasecmp(item, "flashlight")) {
|
||||
printf("It doesn't have any batteries.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Examine toy car */
|
||||
if (!strcasecmp(item, "toy car")) {
|
||||
printf("It is a nice toy car.\nYour grandson Matthew would like it.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Examine old radio */
|
||||
if (!strcasecmp(item, "old radio")) {
|
||||
printf("It is a 1940 Zenith 8-S-563 console\nwith an 8A02 chassis. You'd turn it on\nbut the electricity is off.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Nothing special about this item */
|
||||
printf("You see nothing special about it.\n");
|
||||
}
|
||||
|
||||
/* Use command */
|
||||
void doUse()
|
||||
{
|
||||
char *sp;
|
||||
char *item;
|
||||
|
||||
/* Command line should be like "U[SE] ITEM" Item name will be after after first space. */
|
||||
sp = strchr(buffer, ' ');
|
||||
if (sp == NULL) {
|
||||
printf("Use what?\n");
|
||||
return;
|
||||
}
|
||||
|
||||
item = sp + 1;
|
||||
|
||||
/* Make sure item is being carried or is in the current location */
|
||||
if (!carryingItem(item) && !itemIsHere(item)) {
|
||||
printf("I don't see it here.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
++turnsPlayed;
|
||||
|
||||
/* Use key */
|
||||
if (!strcasecmp(item, "key") && (currentLocation == VacantRoom)) {
|
||||
printf("You insert the key in the door and it\nopens, revealing a tunnel.\n");
|
||||
Move[21][North] = 23;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use pitchfork */
|
||||
if (!strcasecmp(item, "pitchfork") && (currentLocation == WolfTree) && (wolfState == 0)) {
|
||||
printf("You jab the wolf with the pitchfork.\nIt howls and runs away.\n");
|
||||
wolfState = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use toy car */
|
||||
if (!strcasecmp(item, "toy car") && (currentLocation == WolfTree && wolfState == 1)) {
|
||||
printf("You show Matthew the toy car and he\ncomes down to take it. You take Matthew\nin your arms and carry him home.\n");
|
||||
wolfState = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use oil */
|
||||
if (!strcasecmp(item, "oil")) {
|
||||
if (carryingItem("lamp")) {
|
||||
printf("You fill the lamp with oil.\n");
|
||||
lampFilled = 1;
|
||||
return;
|
||||
} else {
|
||||
printf("You don't have anything to use it with.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use matches */
|
||||
if (!strcasecmp(item, "matches")) {
|
||||
if (carryingItem("lamp")) {
|
||||
if (lampFilled) {
|
||||
printf("You light the lamp. You can see!\n");
|
||||
lampLit = 1;
|
||||
return;
|
||||
} else {
|
||||
printf("You can't light the lamp. It needs oil.\n");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
printf("Nothing here to light\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Use candybar */
|
||||
if (!strcasecmp(item, "candybar")) {
|
||||
printf("That hit the spot. You no longer feel\nhungry.\n");
|
||||
ateFood = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use bottle */
|
||||
if (!strcasecmp(item, "bottle")) {
|
||||
if (currentLocation == Cistern) {
|
||||
printf("You fill the bottle with water from the\ncistern and take a drink. You no longer\nfeel thirsty.\n");
|
||||
drankWater = 1;
|
||||
return;
|
||||
} else {
|
||||
printf("The bottle is empty. If only you had\nsome water to fill it!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use stale meat */
|
||||
if (!strcasecmp(item, "stale meat")) {
|
||||
printf("The meat looked and tasted bad. You\nfeel very sick and pass out.\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Default */
|
||||
printf("Nothing happens\n");
|
||||
}
|
||||
|
||||
/* Prompt user and get a line of input */
|
||||
void prompt()
|
||||
{
|
||||
printf("? ");
|
||||
fgets(buffer, sizeof(buffer)-1, stdin);
|
||||
|
||||
/* Remove trailing newline */
|
||||
buffer[strlen(buffer)-1] = '\0';
|
||||
}
|
||||
|
||||
/* Do special things unrelated to command typed. */
|
||||
void doActions()
|
||||
{
|
||||
if ((turnsPlayed == 10) && !lampLit) {
|
||||
printf("It will be getting dark soon. You need\nsome kind of light or soon you won't\nbe able to see.\n");
|
||||
}
|
||||
|
||||
if ((turnsPlayed >= 60) && (!lampLit || (!itemIsHere("lamp") && !carryingItem("lamp")))) {
|
||||
printf("It is dark out and you have no light.\nYou stumble around for a while and\nthen fall, hit your head, and pass out.\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((turnsPlayed == 20) && !drankWater) {
|
||||
printf("You are getting very thirsty.\nYou need to get a drink soon.\n");
|
||||
}
|
||||
|
||||
if ((turnsPlayed == 30) && !ateFood) {
|
||||
printf("You are getting very hungry.\nYou need to find something to eat.\n");
|
||||
}
|
||||
|
||||
if ((turnsPlayed == 50) && !drankWater) {
|
||||
printf("You pass out due to thirst.\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((turnsPlayed == 40) && !ateFood) {
|
||||
printf("You pass out from hunger.\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentLocation == Tunnel) {
|
||||
if (itemIsHere("cheese")) {
|
||||
printf("The rats go after the cheese.\n");
|
||||
} else {
|
||||
if (ratAttack < 3) {
|
||||
printf("The rats are coming towards you!\n");
|
||||
++ratAttack;
|
||||
} else {
|
||||
printf("The rats attack and you pass out.\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* wolfState values: 0 - wolf attacking 1 - wolf gone, Matthew in tree. 2 - Matthew safe, you won. Game over. */
|
||||
if (currentLocation == WolfTree) {
|
||||
switch (wolfState) {
|
||||
case 0:
|
||||
printf("A wolf is circling around the tree.\nMatthew is up in the tree. You have to\nsave him! If only you had some kind of\nweapon!\n");
|
||||
break;
|
||||
case 1:
|
||||
printf("Matthew is afraid to come\ndown from the tree. If only you had\nsomething to coax him with.\n");
|
||||
break;
|
||||
case 2:
|
||||
printf("Congratulations! You succeeded and won\nthe game. I hope you had as much fun\nplaying the game as I did creating it.\n- Jeff Tranter <tranter@pobox.com>\n");
|
||||
gameOver = 1;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set variables to values for start of game */
|
||||
void initialize()
|
||||
{
|
||||
currentLocation = Driveway1;
|
||||
lampFilled = 0;
|
||||
lampLit = 0;
|
||||
ateFood = 0;
|
||||
drankWater = 0;
|
||||
ratAttack = 0;
|
||||
wolfState = 0;
|
||||
turnsPlayed = 0;
|
||||
gameOver= 0;
|
||||
|
||||
/* These doors can get changed during game and may need to be reset O*/
|
||||
Move[17][North] = 0;
|
||||
Move[21][North] = 0;
|
||||
|
||||
/* Set inventory to default */
|
||||
memset(Inventory, 0, sizeof(Inventory[0])*MAXITEMS);
|
||||
Inventory[0] = Flashlight;
|
||||
|
||||
/* Put items in their default locations */
|
||||
locationOfItem[0] = 0; /* NoItem */
|
||||
locationOfItem[1] = Driveway1; /* Key */
|
||||
locationOfItem[2] = Hayloft; /* Pitchfork */
|
||||
locationOfItem[3] = 0; /* Flashlight */
|
||||
locationOfItem[4] = WorkRoom; /* Lamp */
|
||||
locationOfItem[5] = Garage; /* Oil */
|
||||
locationOfItem[6] = Kitchen; /* Candybar */
|
||||
locationOfItem[7] = Driveway2; /* Bottle */
|
||||
locationOfItem[8] = GirlsBedroom; /* Doll */
|
||||
locationOfItem[9] = BoysBedroom; /* ToyCar */
|
||||
locationOfItem[10] = ServantsQuarters; /* Matches */
|
||||
locationOfItem[11] = Woods25; /* GoldCoin */
|
||||
locationOfItem[12] = Woods29; /* SilverCoin */
|
||||
locationOfItem[13] = DiningRoom; /* StaleMeat */
|
||||
locationOfItem[14] = DrawingRoom; /* Book */
|
||||
locationOfItem[15] = LaundryRoom; /* Cheese */
|
||||
locationOfItem[16] = MasterBedroom; /* OldRadio */
|
||||
}
|
||||
|
||||
/* Main program (obviously) */
|
||||
int main(void)
|
||||
{
|
||||
while (1) {
|
||||
initialize();
|
||||
clearScreen();
|
||||
printf("%s", introText);
|
||||
|
||||
while (!gameOver) {
|
||||
prompt();
|
||||
if (buffer[0] == '\0') {
|
||||
} else if (tolower(buffer[0]) == 'h') {
|
||||
doHelp();
|
||||
} else if (tolower(buffer[0]) == 'i') {
|
||||
doInventory();
|
||||
} else if ((tolower(buffer[0]) == 'g')
|
||||
|| !strcasecmp(buffer, "n") || !strcasecmp(buffer, "s")
|
||||
|| !strcasecmp(buffer, "e") || !strcasecmp(buffer, "w")
|
||||
|| !strcasecmp(buffer, "u") || !strcasecmp(buffer, "d")
|
||||
|| !strcasecmp(buffer, "north") || !strcasecmp(buffer, "south")
|
||||
|| !strcasecmp(buffer, "east") || !strcasecmp(buffer, "west")
|
||||
|| !strcasecmp(buffer, "up") || !strcasecmp(buffer, "down")) {
|
||||
doGo();
|
||||
} else if (tolower(buffer[0]) == 'l') {
|
||||
doLook();
|
||||
} else if (tolower(buffer[0]) == 't') {
|
||||
doTake();
|
||||
} else if (tolower(buffer[0]) == 'e') {
|
||||
doExamine();
|
||||
} else if (tolower(buffer[0]) == 'u') {
|
||||
doUse();
|
||||
} else if (tolower(buffer[0]) == 'd') {
|
||||
doDrop();
|
||||
} else if (tolower(buffer[0]) == 'q') {
|
||||
doQuit();
|
||||
} else if (!strcasecmp(buffer, "xyzzy")) {
|
||||
printf("Nice try, but that won't work here.\n");
|
||||
} else {
|
||||
printf("I don't understand. Try 'help'.\n");
|
||||
}
|
||||
|
||||
/* Handle special actions. */
|
||||
doActions();
|
||||
}
|
||||
|
||||
printf("Game over after %d turns.\n", turnsPlayed);
|
||||
printf("%s", "Do you want to play again (y/n)? ");
|
||||
fgets(buffer, sizeof(buffer)-1, stdin);
|
||||
if (tolower(buffer[0]) == 'n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
.segment "INIT"
|
||||
.segment "ONCE"
|
||||
; don't use $800-$1fff :(
|
||||
.segment "STARTUP"
|
||||
jmp Start
|
||||
; use $4000- :)
|
||||
.segment "CODE"
|
||||
|
||||
.define EQU =
|
||||
|
@ -146,7 +149,7 @@ PERFECT_AIM EQU $1
|
|||
;==============================
|
||||
; back up part of the zero page
|
||||
;==============================
|
||||
|
||||
Start:
|
||||
lda #>zero_page_save
|
||||
sta BASH
|
||||
lda #<zero_page_save
|
||||
|
@ -3029,7 +3032,7 @@ check_paddles:
|
|||
LDY #0 ;INIT COUNTER
|
||||
NOP ;COMPENSATE FOR 1ST COUNT
|
||||
NOP
|
||||
PREAD2: LDA PADDL0,X ;COUNT EVERY 11 µSEC.
|
||||
PREAD2: LDA PADDL0,X ;COUNT EVERY 11 uSEC.
|
||||
BPL RTS2D ;BRANCH WHEN TIMED OUT
|
||||
INY ;INCREMENT COUNTER
|
||||
BNE PREAD2 ;CONTINUE COUNTING
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,518 @@
|
|||
|
||||
;;{w:8,h:16,bpp:2,count:128,brev:1,flip:1,remap:[0,8,9,10,11,1,2,3,4,5,6,7]};;
|
||||
.segment "CHR0"
|
||||
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$14,$05,$50,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$05,$50
|
||||
.byte $00,$00,$05,$50,$00,$00,$55,$55
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$15,$50
|
||||
.byte $00,$00,$00,$00,$15,$40,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $14,$00,$00,$14,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$55,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$14,$14,$14,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$50,$05,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$14,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$05,$00
|
||||
.byte $00,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$14
|
||||
.byte $00,$00,$00,$00,$00,$50,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $14,$00,$00,$14,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$01,$40,$00,$00,$00,$50
|
||||
.byte $01,$40,$05,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$14,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$14,$00,$14,$55,$55,$15,$54
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$54,$00,$00,$00,$00
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$50,$00,$00,$00,$00,$14,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$50
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$05,$00
|
||||
.byte $00,$14,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$14
|
||||
.byte $00,$00,$00,$00,$00,$50,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $14,$00,$00,$14,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$50,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$00,$15,$54,$15,$54,$00,$40
|
||||
.byte $00,$00,$05,$54,$05,$54,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$15,$40
|
||||
.byte $01,$40,$14,$00,$14,$55,$00,$00
|
||||
.byte $14,$00,$00,$14,$01,$40,$14,$14
|
||||
.byte $00,$14,$01,$50,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$15,$54,$01,$40
|
||||
.byte $00,$00,$01,$40,$00,$00,$05,$14
|
||||
.byte $05,$50,$01,$45,$05,$45,$00,$00
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $01,$50,$00,$00,$01,$50,$14,$00
|
||||
.byte $01,$54,$00,$50,$15,$54,$05,$50
|
||||
.byte $00,$14,$15,$40,$05,$50,$05,$00
|
||||
.byte $05,$50,$05,$40,$01,$50,$01,$50
|
||||
.byte $00,$14,$00,$00,$14,$00,$01,$40
|
||||
.byte $15,$55,$14,$14,$15,$50,$05,$50
|
||||
.byte $15,$40,$15,$54,$14,$00,$05,$54
|
||||
.byte $14,$14,$05,$50,$05,$50,$14,$14
|
||||
.byte $15,$54,$14,$05,$14,$05,$05,$50
|
||||
.byte $14,$00,$05,$50,$14,$14,$05,$50
|
||||
.byte $01,$40,$05,$50,$01,$40,$05,$14
|
||||
.byte $14,$14,$01,$40,$15,$54,$05,$00
|
||||
.byte $00,$14,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$05,$54,$15,$50,$05,$50
|
||||
.byte $05,$54,$05,$50,$05,$00,$05,$54
|
||||
.byte $14,$14,$15,$54,$00,$50,$14,$14
|
||||
.byte $15,$54,$14,$05,$14,$14,$05,$50
|
||||
.byte $15,$50,$05,$54,$14,$00,$15,$50
|
||||
.byte $01,$54,$05,$54,$01,$40,$05,$14
|
||||
.byte $14,$14,$05,$50,$15,$54,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$15,$54
|
||||
.byte $00,$00,$50,$05,$55,$55,$01,$50
|
||||
.byte $00,$40,$01,$50,$01,$50,$00,$00
|
||||
.byte $55,$55,$05,$50,$50,$05,$50,$50
|
||||
.byte $05,$50,$55,$00,$55,$15,$01,$40
|
||||
.byte $15,$00,$00,$54,$05,$50,$14,$14
|
||||
.byte $00,$14,$05,$50,$00,$00,$05,$50
|
||||
.byte $01,$40,$05,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$15,$54,$01,$40
|
||||
.byte $00,$00,$01,$40,$00,$00,$05,$14
|
||||
.byte $14,$14,$11,$45,$14,$14,$00,$00
|
||||
.byte $05,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $01,$50,$00,$00,$01,$50,$05,$00
|
||||
.byte $05,$05,$00,$50,$14,$00,$14,$14
|
||||
.byte $00,$14,$00,$50,$14,$14,$05,$00
|
||||
.byte $14,$14,$01,$40,$01,$50,$01,$50
|
||||
.byte $00,$50,$00,$00,$05,$00,$01,$40
|
||||
.byte $50,$00,$14,$14,$14,$14,$14,$14
|
||||
.byte $14,$50,$14,$00,$14,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$14,$14,$14,$14
|
||||
.byte $14,$00,$14,$05,$14,$05,$14,$14
|
||||
.byte $14,$00,$14,$14,$14,$14,$14,$14
|
||||
.byte $01,$40,$14,$14,$05,$50,$05,$14
|
||||
.byte $14,$14,$01,$40,$14,$00,$05,$00
|
||||
.byte $00,$50,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$14,$14,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$00,$05,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$50,$14,$14
|
||||
.byte $01,$40,$14,$45,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$00,$00,$14
|
||||
.byte $05,$00,$14,$14,$05,$50,$05,$14
|
||||
.byte $14,$14,$14,$14,$14,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$14,$14
|
||||
.byte $00,$00,$41,$41,$54,$15,$05,$54
|
||||
.byte $01,$50,$04,$44,$04,$44,$01,$40
|
||||
.byte $54,$15,$14,$14,$41,$41,$50,$50
|
||||
.byte $01,$40,$15,$00,$15,$05,$15,$54
|
||||
.byte $15,$40,$01,$54,$15,$54,$00,$00
|
||||
.byte $01,$54,$14,$14,$00,$00,$15,$54
|
||||
.byte $01,$40,$15,$54,$01,$40,$01,$40
|
||||
.byte $00,$00,$04,$10,$15,$54,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$15,$55
|
||||
.byte $00,$14,$14,$54,$14,$14,$00,$00
|
||||
.byte $05,$00,$00,$50,$05,$14,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$05,$00
|
||||
.byte $05,$45,$00,$50,$05,$00,$14,$14
|
||||
.byte $15,$55,$00,$14,$14,$14,$05,$00
|
||||
.byte $14,$14,$00,$50,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$00,$01,$40,$00,$00
|
||||
.byte $50,$55,$14,$14,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$00,$14,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$14,$14,$14,$50
|
||||
.byte $14,$00,$14,$05,$14,$05,$14,$14
|
||||
.byte $14,$00,$14,$14,$14,$14,$00,$14
|
||||
.byte $01,$40,$14,$14,$14,$14,$05,$14
|
||||
.byte $14,$14,$01,$40,$14,$00,$05,$00
|
||||
.byte $00,$50,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$14,$14,$14,$14,$14,$00
|
||||
.byte $14,$14,$14,$00,$05,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$50,$14,$50
|
||||
.byte $01,$40,$14,$45,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$00,$00,$14
|
||||
.byte $05,$00,$14,$14,$14,$14,$14,$45
|
||||
.byte $05,$50,$14,$14,$05,$00,$05,$00
|
||||
.byte $01,$40,$00,$50,$00,$00,$14,$14
|
||||
.byte $00,$00,$45,$51,$50,$05,$15,$55
|
||||
.byte $05,$54,$15,$15,$15,$55,$05,$50
|
||||
.byte $50,$05,$50,$05,$05,$50,$50,$50
|
||||
.byte $05,$50,$05,$00,$05,$05,$05,$50
|
||||
.byte $15,$50,$05,$54,$01,$40,$14,$14
|
||||
.byte $05,$54,$14,$14,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$50,$05,$00
|
||||
.byte $00,$00,$14,$14,$05,$50,$05,$50
|
||||
.byte $00,$00,$01,$40,$00,$00,$05,$14
|
||||
.byte $00,$50,$05,$00,$14,$55,$00,$00
|
||||
.byte $05,$00,$00,$50,$01,$50,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$01,$40
|
||||
.byte $05,$45,$00,$50,$01,$40,$00,$14
|
||||
.byte $14,$14,$00,$14,$14,$14,$01,$40
|
||||
.byte $14,$54,$05,$54,$00,$00,$00,$00
|
||||
.byte $05,$00,$15,$54,$00,$50,$01,$40
|
||||
.byte $51,$45,$15,$54,$14,$14,$14,$00
|
||||
.byte $14,$14,$14,$00,$14,$00,$14,$54
|
||||
.byte $14,$14,$01,$40,$00,$14,$14,$50
|
||||
.byte $14,$00,$14,$45,$14,$15,$14,$14
|
||||
.byte $14,$00,$14,$14,$14,$50,$00,$50
|
||||
.byte $01,$40,$14,$14,$14,$14,$14,$45
|
||||
.byte $04,$50,$01,$40,$05,$00,$05,$00
|
||||
.byte $01,$40,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$05,$54,$14,$14,$14,$00
|
||||
.byte $14,$14,$15,$54,$05,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$50,$15,$40
|
||||
.byte $01,$40,$14,$45,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$00,$05,$50
|
||||
.byte $05,$00,$14,$14,$14,$14,$14,$45
|
||||
.byte $01,$40,$14,$14,$01,$40,$14,$00
|
||||
.byte $01,$40,$00,$14,$00,$00,$14,$14
|
||||
.byte $00,$00,$40,$01,$55,$55,$15,$55
|
||||
.byte $15,$55,$15,$15,$15,$55,$05,$50
|
||||
.byte $50,$05,$50,$05,$05,$50,$50,$51
|
||||
.byte $14,$14,$05,$00,$05,$05,$14,$14
|
||||
.byte $15,$54,$15,$54,$01,$40,$14,$14
|
||||
.byte $15,$54,$05,$50,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$15,$54,$15,$54
|
||||
.byte $15,$54,$55,$55,$05,$50,$05,$50
|
||||
.byte $00,$00,$01,$40,$00,$00,$05,$14
|
||||
.byte $01,$40,$01,$40,$14,$00,$00,$00
|
||||
.byte $05,$00,$00,$50,$15,$55,$15,$54
|
||||
.byte $00,$00,$15,$54,$00,$00,$01,$40
|
||||
.byte $05,$05,$00,$50,$00,$50,$01,$50
|
||||
.byte $05,$14,$15,$50,$14,$14,$01,$40
|
||||
.byte $05,$50,$14,$14,$00,$00,$00,$00
|
||||
.byte $14,$00,$00,$00,$00,$14,$01,$40
|
||||
.byte $51,$45,$14,$14,$15,$50,$14,$00
|
||||
.byte $14,$14,$15,$50,$15,$50,$14,$00
|
||||
.byte $15,$54,$01,$40,$00,$14,$15,$40
|
||||
.byte $14,$00,$14,$45,$14,$55,$14,$14
|
||||
.byte $15,$50,$14,$14,$15,$50,$01,$40
|
||||
.byte $01,$40,$14,$14,$14,$14,$14,$45
|
||||
.byte $01,$40,$05,$50,$01,$40,$05,$00
|
||||
.byte $01,$40,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$14,$14,$14,$14,$00
|
||||
.byte $14,$14,$14,$14,$15,$54,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$50,$14,$50
|
||||
.byte $01,$40,$14,$45,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$15,$00,$14,$00
|
||||
.byte $05,$00,$14,$14,$14,$14,$14,$45
|
||||
.byte $05,$50,$14,$14,$00,$50,$05,$00
|
||||
.byte $01,$40,$00,$50,$00,$00,$14,$14
|
||||
.byte $00,$00,$40,$01,$55,$55,$15,$55
|
||||
.byte $05,$54,$04,$44,$05,$54,$05,$50
|
||||
.byte $50,$05,$50,$05,$05,$50,$15,$51
|
||||
.byte $14,$14,$05,$00,$05,$05,$14,$14
|
||||
.byte $15,$50,$05,$54,$01,$40,$14,$14
|
||||
.byte $15,$54,$05,$40,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$50,$05,$00
|
||||
.byte $14,$00,$14,$14,$05,$50,$05,$50
|
||||
.byte $00,$00,$05,$50,$00,$00,$05,$14
|
||||
.byte $05,$00,$00,$50,$05,$40,$00,$00
|
||||
.byte $05,$00,$00,$50,$01,$50,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$50
|
||||
.byte $05,$15,$00,$50,$00,$14,$00,$14
|
||||
.byte $05,$14,$14,$00,$15,$50,$00,$50
|
||||
.byte $15,$14,$14,$14,$01,$50,$01,$50
|
||||
.byte $05,$00,$15,$54,$00,$50,$00,$50
|
||||
.byte $50,$55,$14,$14,$14,$14,$14,$00
|
||||
.byte $14,$14,$14,$00,$14,$00,$14,$00
|
||||
.byte $14,$14,$01,$40,$00,$14,$14,$50
|
||||
.byte $14,$00,$14,$45,$15,$45,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$14,$05,$00
|
||||
.byte $01,$40,$14,$14,$14,$14,$14,$45
|
||||
.byte $01,$40,$14,$14,$00,$50,$05,$00
|
||||
.byte $05,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$14,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$05,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$50,$14,$14
|
||||
.byte $01,$40,$14,$45,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$54,$14,$00
|
||||
.byte $05,$00,$14,$14,$14,$14,$14,$45
|
||||
.byte $14,$14,$14,$14,$00,$14,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$14,$14
|
||||
.byte $00,$00,$44,$11,$51,$45,$15,$15
|
||||
.byte $01,$50,$01,$50,$05,$54,$01,$40
|
||||
.byte $54,$15,$14,$14,$41,$41,$00,$15
|
||||
.byte $14,$14,$05,$54,$05,$55,$05,$50
|
||||
.byte $15,$40,$01,$54,$15,$54,$14,$14
|
||||
.byte $15,$54,$14,$00,$00,$00,$15,$54
|
||||
.byte $15,$54,$01,$40,$01,$40,$01,$40
|
||||
.byte $14,$00,$04,$10,$01,$40,$15,$54
|
||||
.byte $00,$00,$05,$50,$14,$14,$15,$55
|
||||
.byte $14,$00,$15,$14,$14,$50,$01,$40
|
||||
.byte $01,$40,$01,$40,$05,$14,$01,$40
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$50
|
||||
.byte $05,$15,$15,$50,$14,$14,$14,$14
|
||||
.byte $05,$14,$14,$00,$05,$00,$00,$50
|
||||
.byte $14,$14,$14,$14,$01,$50,$01,$50
|
||||
.byte $01,$40,$00,$00,$01,$40,$14,$14
|
||||
.byte $50,$05,$14,$14,$14,$14,$14,$14
|
||||
.byte $14,$14,$14,$00,$14,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$14,$14,$50
|
||||
.byte $14,$00,$15,$15,$15,$05,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$14,$14,$00
|
||||
.byte $01,$40,$14,$14,$14,$14,$14,$05
|
||||
.byte $05,$10,$14,$14,$00,$14,$05,$00
|
||||
.byte $05,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$05,$50,$15,$50,$05,$50
|
||||
.byte $05,$54,$05,$50,$05,$00,$05,$54
|
||||
.byte $15,$50,$15,$40,$05,$50,$14,$14
|
||||
.byte $01,$40,$15,$54,$15,$50,$05,$50
|
||||
.byte $15,$50,$05,$54,$14,$14,$05,$54
|
||||
.byte $15,$54,$14,$14,$14,$14,$14,$05
|
||||
.byte $14,$14,$14,$14,$15,$54,$01,$40
|
||||
.byte $01,$40,$01,$40,$40,$54,$14,$14
|
||||
.byte $00,$00,$44,$11,$51,$45,$04,$04
|
||||
.byte $00,$40,$01,$50,$01,$50,$00,$00
|
||||
.byte $55,$55,$05,$50,$50,$05,$00,$05
|
||||
.byte $14,$14,$05,$04,$05,$05,$15,$54
|
||||
.byte $15,$00,$00,$54,$05,$50,$14,$14
|
||||
.byte $05,$54,$14,$14,$00,$00,$05,$50
|
||||
.byte $05,$50,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$01,$40,$15,$54
|
||||
.byte $00,$00,$05,$50,$14,$14,$05,$14
|
||||
.byte $14,$14,$51,$44,$14,$50,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$14
|
||||
.byte $05,$05,$01,$50,$14,$14,$14,$14
|
||||
.byte $05,$00,$14,$00,$01,$40,$00,$14
|
||||
.byte $14,$14,$14,$14,$00,$00,$00,$00
|
||||
.byte $00,$50,$00,$00,$05,$00,$14,$14
|
||||
.byte $50,$05,$05,$50,$14,$14,$14,$14
|
||||
.byte $14,$50,$14,$00,$14,$00,$14,$14
|
||||
.byte $14,$14,$01,$40,$00,$14,$14,$14
|
||||
.byte $14,$00,$14,$05,$14,$05,$14,$14
|
||||
.byte $14,$14,$14,$14,$14,$14,$14,$14
|
||||
.byte $01,$40,$14,$14,$14,$14,$14,$05
|
||||
.byte $14,$14,$14,$14,$00,$14,$05,$00
|
||||
.byte $14,$00,$00,$50,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$14,$00,$00,$00
|
||||
.byte $00,$14,$00,$00,$05,$00,$00,$00
|
||||
.byte $14,$00,$00,$00,$00,$00,$14,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $05,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$51,$45,$05,$50
|
||||
.byte $00,$00,$50,$05,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$40,$00,$40,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$55
|
||||
.byte $05,$50,$05,$54,$05,$55,$01,$40
|
||||
.byte $14,$00,$00,$14,$01,$40,$14,$14
|
||||
.byte $01,$55,$05,$50,$00,$00,$01,$40
|
||||
.byte $01,$40,$01,$40,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$01,$40,$15,$54
|
||||
.byte $00,$00,$01,$40,$14,$14,$05,$14
|
||||
.byte $05,$50,$51,$40,$05,$40,$01,$40
|
||||
.byte $00,$50,$05,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$14
|
||||
.byte $01,$54,$00,$50,$05,$50,$05,$50
|
||||
.byte $05,$00,$15,$54,$01,$50,$15,$54
|
||||
.byte $05,$50,$05,$50,$00,$00,$00,$00
|
||||
.byte $00,$14,$00,$00,$14,$00,$05,$50
|
||||
.byte $15,$54,$01,$40,$15,$50,$05,$50
|
||||
.byte $15,$40,$15,$54,$15,$54,$05,$50
|
||||
.byte $14,$14,$05,$50,$00,$14,$14,$14
|
||||
.byte $14,$00,$14,$05,$14,$05,$05,$50
|
||||
.byte $15,$50,$05,$50,$15,$50,$05,$50
|
||||
.byte $15,$54,$14,$14,$14,$14,$14,$05
|
||||
.byte $14,$14,$14,$14,$15,$54,$05,$50
|
||||
.byte $14,$00,$05,$50,$14,$14,$00,$00
|
||||
.byte $00,$50,$00,$00,$14,$00,$00,$00
|
||||
.byte $00,$14,$00,$00,$01,$54,$00,$00
|
||||
.byte $14,$00,$01,$40,$00,$50,$14,$00
|
||||
.byte $15,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $05,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$50
|
||||
.byte $01,$40,$05,$00,$15,$01,$01,$40
|
||||
.byte $00,$00,$15,$54,$15,$54,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$15,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$05,$50,$00,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$01,$40,$00,$50,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $01,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$01,$40,$00,$00
|
||||
.byte $05,$40,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $55,$55,$00,$00,$55,$55,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
.byte $00,$00,$00,$00,$00,$00,$00,$00
|
||||
|
||||
;; 4096 bytes
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
#include "atari7800.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
byte DL[16][64];
|
||||
|
||||
DLLEntry DLL[16];
|
||||
|
||||
//#link "chr_font.s"
|
||||
|
||||
char* hello = "\2\4\6\0\220\222\102";
|
||||
|
||||
void setup_dll() {
|
||||
byte i;
|
||||
// set up DLL
|
||||
byte* dlptr = &DL[0][0];
|
||||
DLLEntry *dll = &DLL[0];
|
||||
for (i=0; i<16; i++) {
|
||||
dll->offset_flags = DLL_H16 | (16-1);
|
||||
dll->dl_hi = (word)dlptr>>8;
|
||||
dll->dl_lo = (byte)dlptr;
|
||||
dlptr += sizeof(DL[0]);
|
||||
dll++;
|
||||
}
|
||||
memset(DL, 0, sizeof(DL));
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
setup_dll();
|
||||
|
||||
// set up display list
|
||||
{
|
||||
DL5Entry dl5;
|
||||
dl5.data_hi = (word)hello>>8;
|
||||
dl5.data_lo = (word)hello;
|
||||
dl5.flags = DL5_INDIRECT;
|
||||
dl5.width_pal = 32-8;
|
||||
dl5.xpos = 32;
|
||||
memcpy(DL[2], &dl5, sizeof(dl5));
|
||||
}
|
||||
|
||||
// activate DMA
|
||||
MARIA.CHARBASE = 0x80;
|
||||
MARIA.DPPH = (word)DLL>>8;
|
||||
MARIA.DPPL = (byte)DLL;
|
||||
MARIA.CTRL = CTRL_DMA_ON | CTRL_DBLBYTE | CTRL_160AB;
|
||||
MARIA.P0C1 = 0x2f;
|
||||
MARIA.P0C2 = 0x1f;
|
||||
MARIA.P0C3 = 0xf;
|
||||
|
||||
while (1) {
|
||||
while ((MARIA.MSTAT & MSTAT_VBLANK) == 0) ;
|
||||
while ((MARIA.MSTAT & MSTAT_VBLANK) != 0) ;
|
||||
MARIA.BACKGRND = 0;
|
||||
DL[2][4]++;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
#include "atari7800.h"
|
||||
|
||||
void main() {
|
||||
byte y1 = 110; // Y start of bar
|
||||
byte y2 = 10; // height of bar
|
||||
byte i;
|
||||
while (1) {
|
||||
// wait for end of frame / start of vblank
|
||||
while ((MARIA.MSTAT & MSTAT_VBLANK) == 0) ;
|
||||
// wait for end of vblank / start of frame
|
||||
while ((MARIA.MSTAT & MSTAT_VBLANK) != 0) ;
|
||||
// skip scanlines until at top of bar
|
||||
for (i=0; i<y1; i++) {
|
||||
WSYNC();
|
||||
}
|
||||
// set the background color on each scanline
|
||||
for (i=0; i<y2; i++) {
|
||||
MARIA.BACKGRND = P6532.SWCHA - i;
|
||||
WSYNC();
|
||||
}
|
||||
// clear the background after the final scanline
|
||||
MARIA.BACKGRND = 0;
|
||||
// move bar slowly downward
|
||||
y1++;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
. ./scripts/env.sh
|
||||
VERSION=`git tag | tail -1`
|
||||
#VERSION=`git tag -l HEAD`
|
||||
#. ./scripts/env.sh
|
||||
#VERSION=`git tag | tail -1`
|
||||
VERSION=`git tag -l --points-at HEAD`
|
||||
if [ "$VERSION" == "" ]; then
|
||||
echo "No version at HEAD! Tag it first!"
|
||||
exit 1
|
||||
|
|
65
src/audio.ts
65
src/audio.ts
|
@ -1,4 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
// from TSS
|
||||
declare var MasterChannel, AudioLooper, PsgDeviceChannel;
|
||||
|
@ -402,10 +401,11 @@ export var SampleAudio = function(clockfreq) {
|
|||
self.sr=self.context.sampleRate;
|
||||
self.bufferlen=2048;
|
||||
|
||||
// Amiga 500 fixed filter at 6kHz. WebAudio lowpass is 12dB/oct, whereas
|
||||
// older Amigas had a 6dB/oct filter at 4900Hz.
|
||||
// remove DC bias
|
||||
self.filterNode=self.context.createBiquadFilter();
|
||||
self.filterNode.frequency.value=6000;
|
||||
self.filterNode.type='lowshelf';
|
||||
self.filterNode.frequency.value=100;
|
||||
self.filterNode.gain.value=-6;
|
||||
|
||||
// mixer
|
||||
if ( typeof self.context.createScriptProcessor === 'function') {
|
||||
|
@ -471,7 +471,7 @@ export var SampleAudio = function(clockfreq) {
|
|||
var inext = (ifill + 1) % bufferlist.length;
|
||||
if (inext == idrain) {
|
||||
ifill = Math.floor(idrain + nbuffers/2) % bufferlist.length;
|
||||
//console.log('audio skipped', idrain, ifill);
|
||||
//console.log('SampleAudio: skipped buffer', idrain, ifill); // TODO
|
||||
} else {
|
||||
ifill = inext;
|
||||
}
|
||||
|
@ -480,15 +480,52 @@ export var SampleAudio = function(clockfreq) {
|
|||
}
|
||||
|
||||
this.feedSample = function(value, count) {
|
||||
while (count-- > 0) {
|
||||
accum += value;
|
||||
sfrac += sinc;
|
||||
while (sfrac >= 1) {
|
||||
sfrac -= 1;
|
||||
value *= sfrac;
|
||||
this.addSingleSample(accum - value);
|
||||
accum = value;
|
||||
}
|
||||
accum += value * count;
|
||||
sfrac += sinc * count;
|
||||
while (sfrac >= 1) {
|
||||
sfrac -= 1;
|
||||
value *= sfrac;
|
||||
this.addSingleSample(accum - value);
|
||||
accum = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class SampledAudio {
|
||||
sa;
|
||||
constructor(sampleRate : number) {
|
||||
this.sa = new SampleAudio(sampleRate);
|
||||
}
|
||||
feedSample(value:number, count:number) {
|
||||
this.sa.feedSample(value, count);
|
||||
}
|
||||
start() {
|
||||
this.sa.start();
|
||||
}
|
||||
stop() {
|
||||
this.sa.stop();
|
||||
}
|
||||
}
|
||||
|
||||
import { SampledAudioSink } from "./devices";
|
||||
|
||||
export class TssChannelAdapter {
|
||||
channel;
|
||||
audioGain = 1.0 / 8192;
|
||||
constructor(channel, oversample:number, sampleRate:number) {
|
||||
this.channel = channel;
|
||||
channel.setBufferLength(oversample*2);
|
||||
channel.setSampleRate(sampleRate);
|
||||
}
|
||||
generate(sink:SampledAudioSink) {
|
||||
var buf = this.channel.getBuffer();
|
||||
var l = buf.length;
|
||||
this.channel.generate(l);
|
||||
for (let i=0; i<l; i+=2)
|
||||
sink.feedSample(buf[i] * this.audioGain, 1);
|
||||
//if (Math.random() < 0.001) console.log(sink);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,17 +33,14 @@ function require(modname) {
|
|||
}
|
||||
|
||||
importScripts("../../gen/emu.js");
|
||||
importScripts("../cpu/z80.js");
|
||||
|
||||
window.buildZ80({
|
||||
applyContention: false
|
||||
});
|
||||
importScripts("../../gen/cpu/ZilogZ80.js");
|
||||
|
||||
var cpu, ram, rom, membus, iobus;
|
||||
var audio;
|
||||
var command = 0;
|
||||
var dac = 0;
|
||||
var current_buffer;
|
||||
var tstates;
|
||||
var last_tstate;
|
||||
|
||||
var timer;
|
||||
|
@ -62,7 +59,7 @@ rom = new RAM(0x4000).mem;
|
|||
rom.fill(0x76); // HALT opcodes
|
||||
|
||||
function fillBuffer() {
|
||||
var t = cpu.getTstates() / cpuAudioFactor;
|
||||
var t = tstates / cpuAudioFactor;
|
||||
while (last_tstate < t) {
|
||||
current_buffer[last_tstate*2] = dac;
|
||||
current_buffer[last_tstate*2+1] = dac;
|
||||
|
@ -91,25 +88,24 @@ function start() {
|
|||
fillBuffer();
|
||||
}
|
||||
};
|
||||
cpu = window.Z80({
|
||||
display: {},
|
||||
memory: membus,
|
||||
ioBus: iobus
|
||||
});
|
||||
cpu = new exports.Z80();
|
||||
cpu.connectMemoryBus(membus);
|
||||
cpu.connectIOBus(iobus);
|
||||
current_buffer = new Int16Array(bufferLength);
|
||||
console.log('started audio');
|
||||
}
|
||||
|
||||
function timerCallback() {
|
||||
cpu.setTstates(0);
|
||||
tstates = 0;
|
||||
last_tstate = 0;
|
||||
var numStates = Math.floor(bufferLength * cpuAudioFactor / numChannels);
|
||||
cpu.runFrame(numStates);
|
||||
//console.log(numStates, cpu.getTstates());
|
||||
cpu.setTstates(numStates); // TODO?
|
||||
while (tstates < numStates) {
|
||||
tstates += cpu.advanceInsn();
|
||||
}
|
||||
tstates = 0;
|
||||
fillBuffer();
|
||||
postMessage({samples:current_buffer});
|
||||
if (!cpu.getHalted()) {
|
||||
if (!cpu.saveState().halted) {
|
||||
curTime += timerPeriod;
|
||||
var dt = curTime - new Date().getTime();
|
||||
if (dt < 0) dt = 0;
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller } from "./emu";
|
||||
import { hex, printFlags, invertMap } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { Segment } from "./workertypes";
|
||||
import { disassemble6502 } from "./cpu/disasm6502";
|
||||
import { disassembleZ80 } from "./cpu/disasmz80";
|
||||
import { Z80 } from "./cpu/ZilogZ80";
|
||||
|
||||
declare var buildZ80, jt, CPU6809;
|
||||
declare var jt, CPU6809;
|
||||
|
||||
export interface OpcodeMetadata {
|
||||
minCycles: number;
|
||||
|
@ -17,7 +19,6 @@ export interface OpcodeMetadata {
|
|||
export interface CpuState {
|
||||
PC:number;
|
||||
EPC?:number; // effective PC (for bankswitching)
|
||||
T?:number;
|
||||
o?:number;/*opcode*/
|
||||
SP?:number
|
||||
/*
|
||||
|
@ -27,8 +28,8 @@ export interface CpuState {
|
|||
export interface EmuState {
|
||||
c?:CpuState, // CPU state
|
||||
b?:Uint8Array|number[], // RAM (TODO: not for vcs, support Uint8Array)
|
||||
ram?:Uint8Array,
|
||||
o?:{}, // verilog
|
||||
T?:number, // verilog
|
||||
};
|
||||
export interface EmuControlsState {
|
||||
}
|
||||
|
@ -49,13 +50,21 @@ export class DebugSymbols {
|
|||
this.symbolmap = symbolmap;
|
||||
this.addr2symbol = invertMap(symbolmap);
|
||||
//// TODO: shouldn't be necc.
|
||||
if (!this.addr2symbol[0x0]) this.addr2symbol[0x0] = '__START__'; // needed for ...
|
||||
if (!this.addr2symbol[0x0]) this.addr2symbol[0x0] = ' '; // needed for ...
|
||||
this.addr2symbol[0x10000] = '__END__'; // ... dump memory to work
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlatformMetadata {
|
||||
name : string; // TODO
|
||||
type MemoryMapType = "main" | "vram";
|
||||
type MemoryMap = { [type:string] : Segment[] };
|
||||
|
||||
export function isDebuggable(arg:any): arg is Debuggable {
|
||||
return typeof arg.getDebugCategories === 'function';
|
||||
}
|
||||
|
||||
export interface Debuggable {
|
||||
getDebugCategories?() : string[];
|
||||
getDebugInfo?(category:string, state:EmuState) : string;
|
||||
}
|
||||
|
||||
export interface Platform {
|
||||
|
@ -69,7 +78,6 @@ export interface Platform {
|
|||
resume() : void;
|
||||
loadROM(title:string, rom:any); // TODO: Uint8Array
|
||||
loadBIOS?(title:string, rom:Uint8Array);
|
||||
getMetadata?() : PlatformMetadata;
|
||||
|
||||
loadState?(state : EmuState) : void;
|
||||
saveState?() : EmuState;
|
||||
|
@ -80,6 +88,7 @@ export interface Platform {
|
|||
disassemble?(addr:number, readfn:(addr:number)=>number) : DisasmLine;
|
||||
readAddress?(addr:number) : number;
|
||||
readVRAMAddress?(addr:number) : number;
|
||||
|
||||
setFrameRate?(fps:number) : void;
|
||||
getFrameRate?() : number;
|
||||
|
||||
|
@ -95,11 +104,12 @@ export interface Platform {
|
|||
|
||||
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
|
||||
getSP?() : number;
|
||||
getPC?() : number;
|
||||
getOriginPC?() : number;
|
||||
newCodeAnalyzer?() : CodeAnalyzer;
|
||||
|
||||
getDebugCategories?() : string[];
|
||||
getDebugInfo?(category:string, state:EmuState) : string;
|
||||
|
||||
getPlatformName?() : string;
|
||||
getMemoryMap?() : MemoryMap;
|
||||
|
||||
setRecorder?(recorder : EmuRecorder) : void;
|
||||
advance?(novideo? : boolean) : void;
|
||||
|
@ -112,6 +122,9 @@ export interface Platform {
|
|||
getCPUState?() : CpuState;
|
||||
|
||||
debugSymbols? : DebugSymbols;
|
||||
|
||||
startProbing?() : ProbeRecorder;
|
||||
stopProbing?() : void;
|
||||
}
|
||||
|
||||
export interface Preset {
|
||||
|
@ -207,6 +220,7 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
|||
debugTargetClock : number = 0;
|
||||
debugClock : number = 0;
|
||||
breakpoints : BreakpointList = new BreakpointList();
|
||||
frameCount : number = 0;
|
||||
|
||||
abstract getCPUState() : CpuState;
|
||||
abstract readAddress(addr:number) : number;
|
||||
|
@ -235,6 +249,7 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
|||
this.debugClock = 0;
|
||||
this.onBreakpointHit = null;
|
||||
this.clearBreakpoint('debug');
|
||||
this.frameCount = 0;
|
||||
}
|
||||
setDebugCondition(debugCond : DebugCondition) {
|
||||
this.setBreakpoint('debug', debugCond);
|
||||
|
@ -251,8 +266,20 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
|||
this.resume();
|
||||
}
|
||||
preFrame() {
|
||||
// save state before frame, to record any inputs that happened pre-frame
|
||||
if (this.debugCallback && !this.debugBreakState) {
|
||||
// save state every frame and rewind debug clocks
|
||||
this.debugSavedState = this.saveState();
|
||||
this.debugTargetClock -= this.debugClock;
|
||||
this.debugClock = 0;
|
||||
}
|
||||
this.frameCount++;
|
||||
}
|
||||
postFrame() {
|
||||
// reload debug state at end of frame after breakpoint
|
||||
if (this.debugCallback && this.debugBreakState) {
|
||||
this.loadState(this.debugBreakState);
|
||||
}
|
||||
}
|
||||
pollControls() {
|
||||
}
|
||||
|
@ -263,6 +290,82 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
|||
this.advance(novideo);
|
||||
this.postFrame();
|
||||
}
|
||||
// default debugging
|
||||
abstract getSP() : number;
|
||||
abstract getPC() : number;
|
||||
abstract isStable() : boolean;
|
||||
|
||||
evalDebugCondition() {
|
||||
if (this.debugCallback && !this.debugBreakState) {
|
||||
this.debugCallback();
|
||||
}
|
||||
}
|
||||
wasBreakpointHit() : boolean {
|
||||
return this.debugBreakState != null;
|
||||
}
|
||||
breakpointHit(targetClock : number) {
|
||||
console.log(this.debugTargetClock, targetClock, this.debugClock, this.isStable());
|
||||
this.debugTargetClock = targetClock;
|
||||
this.debugBreakState = this.saveState();
|
||||
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
|
||||
this.pause();
|
||||
if (this.onBreakpointHit) {
|
||||
this.onBreakpointHit(this.debugBreakState);
|
||||
}
|
||||
}
|
||||
runEval(evalfunc : DebugEvalCondition) {
|
||||
this.setDebugCondition( () => {
|
||||
if (++this.debugClock >= this.debugTargetClock && this.isStable()) {
|
||||
var cpuState = this.getCPUState();
|
||||
if (evalfunc(cpuState)) {
|
||||
this.breakpointHit(this.debugClock);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
runUntilReturn() {
|
||||
var SP0 = this.getSP();
|
||||
this.runEval( (c:CpuState) : boolean => {
|
||||
return c.SP > SP0;
|
||||
});
|
||||
}
|
||||
runToFrameClock(clock : number) : void {
|
||||
this.restartDebugging();
|
||||
this.debugTargetClock = clock;
|
||||
this.runEval(() : boolean => { return true; });
|
||||
}
|
||||
step() {
|
||||
this.runToFrameClock(this.debugClock+1);
|
||||
}
|
||||
stepBack() {
|
||||
var prevState;
|
||||
var prevClock;
|
||||
var clock0 = this.debugTargetClock;
|
||||
this.restartDebugging();
|
||||
this.debugTargetClock = clock0 - 25; // TODO: depends on CPU
|
||||
this.runEval( (c:CpuState) : boolean => {
|
||||
if (this.debugClock < clock0) {
|
||||
prevState = this.saveState();
|
||||
prevClock = this.debugClock;
|
||||
return false;
|
||||
} else {
|
||||
if (prevState) {
|
||||
this.loadState(prevState);
|
||||
this.debugClock = prevClock;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
runToVsync() {
|
||||
var frame0 = this.frameCount;
|
||||
this.runEval( () : boolean => {
|
||||
return this.frameCount > frame0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
////// 6502
|
||||
|
@ -286,93 +389,9 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
debugPCDelta = -1;
|
||||
fixPC(c) { c.PC = (c.PC + this.debugPCDelta) & 0xffff; return c; }
|
||||
unfixPC(c) { c.PC = (c.PC - this.debugPCDelta) & 0xffff; return c;}
|
||||
|
||||
evalDebugCondition() {
|
||||
if (this.debugCallback && !this.debugBreakState) {
|
||||
this.debugCallback();
|
||||
}
|
||||
}
|
||||
postFrame() {
|
||||
if (this.debugCallback) {
|
||||
if (this.debugBreakState) {
|
||||
// reload debug state at end of frame after breakpoint
|
||||
this.loadState(this.debugBreakState);
|
||||
} else {
|
||||
// save state every frame and rewind debug clocks
|
||||
this.debugSavedState = this.saveState();
|
||||
this.debugTargetClock -= this.debugClock;
|
||||
this.debugClock = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
breakpointHit(targetClock : number) {
|
||||
this.debugTargetClock = targetClock;
|
||||
this.debugBreakState = this.saveState();
|
||||
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
|
||||
this.pause();
|
||||
if (this.onBreakpointHit) {
|
||||
this.onBreakpointHit(this.debugBreakState);
|
||||
}
|
||||
}
|
||||
runEval(evalfunc : DebugEvalCondition) {
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ > this.debugTargetClock) {
|
||||
var cpuState = this.getCPUState();
|
||||
if (evalfunc(cpuState)) {
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
runToFrameClock?(clock : number) : void {
|
||||
this.restartDebugging();
|
||||
this.debugTargetClock = clock;
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ > this.debugTargetClock) {
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
step() {
|
||||
var previousPC = -1;
|
||||
this.setDebugCondition( () => {
|
||||
//console.log(this.debugClock, this.debugTargetClock, this.getCPUState().PC, this.getCPUState());
|
||||
if (this.debugClock++ >= this.debugTargetClock) {
|
||||
var thisState = this.getCPUState();
|
||||
if (previousPC < 0) {
|
||||
previousPC = thisState.PC;
|
||||
} else {
|
||||
// doesn't work w/ endless loops
|
||||
if (thisState.PC != previousPC && thisState.T == 0) {
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
stepBack() {
|
||||
var prevState;
|
||||
var prevClock;
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ >= this.debugTargetClock && prevState) {
|
||||
this.loadState(prevState);
|
||||
this.breakpointHit(prevClock-1);
|
||||
return true;
|
||||
} else if (this.debugClock > this.debugTargetClock-10 && this.debugClock <= this.debugTargetClock+this.debugPCDelta) { // TODO: why this works?
|
||||
if (this.getCPUState().T == 0) {
|
||||
prevState = this.saveState();
|
||||
prevClock = this.debugClock;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
getSP() { return this.getCPUState().SP };
|
||||
getPC() { return this.getCPUState().PC };
|
||||
isStable() { return !this.getCPUState()['T']; }
|
||||
|
||||
newCPU(membus : MemoryBus) {
|
||||
var cpu = new jt.M6502();
|
||||
|
@ -388,19 +407,6 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
||||
}
|
||||
|
||||
runUntilReturn() {
|
||||
var depth = 1;
|
||||
this.runEval( (c:CpuState) => {
|
||||
if (depth <= 0 && c.T == 0)
|
||||
return true;
|
||||
if (c.o == 0x20)
|
||||
depth++;
|
||||
else if (c.o == 0x60 || c.o == 0x40)
|
||||
--depth;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
|
@ -413,8 +419,8 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
getDebugInfo(category:string, state:EmuState) : string {
|
||||
switch (category) {
|
||||
case 'CPU': return cpuStateToLongString_6502(state.c);
|
||||
case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100);
|
||||
case 'Stack': return dumpStackToString(<Platform><any>this, state.b, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
||||
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
||||
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +474,7 @@ export function cpuStateToLongString_Z80(c) {
|
|||
function decodeFlags(flags) {
|
||||
return printFlags(flags, ["S","Z",,"H",,"V","N","C"], true);
|
||||
}
|
||||
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.AF) + "\n"
|
||||
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.AF) + " " + (c.iff1?"I":"-") + (c.iff2?"I":"-") + "\n"
|
||||
+ "SP " + hex(c.SP,4) + " IR " + hex(c.IR,4) + "\n"
|
||||
+ "IX " + hex(c.IX,4) + " IY " + hex(c.IY,4) + "\n"
|
||||
+ "AF " + hex(c.AF,4) + " BC " + hex(c.BC,4) + "\n"
|
||||
|
@ -476,169 +482,43 @@ export function cpuStateToLongString_Z80(c) {
|
|||
;
|
||||
}
|
||||
|
||||
export function BusProbe(bus : MemoryBus) {
|
||||
var active = false;
|
||||
var callback;
|
||||
this.activate = function(_callback) {
|
||||
active = true;
|
||||
callback = _callback;
|
||||
}
|
||||
this.deactivate = function() {
|
||||
active = false;
|
||||
callback = null;
|
||||
}
|
||||
this.read = function(a) {
|
||||
if (active) {
|
||||
callback(a);
|
||||
}
|
||||
return bus.read(a);
|
||||
}
|
||||
this.write = function(a,v) {
|
||||
if (active) {
|
||||
callback(a,v);
|
||||
}
|
||||
bus.write(a,v);
|
||||
}
|
||||
this.contend = function(addr,cyc) {
|
||||
return bus.contend(addr,cyc);
|
||||
}
|
||||
this.isContended = function(addr) {
|
||||
return bus.isContended(addr);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseZ80Platform extends BaseDebugPlatform {
|
||||
|
||||
_cpu;
|
||||
probe;
|
||||
waitCycles : number = 0;
|
||||
|
||||
newCPU(membus : MemoryBus, iobus : MemoryBus, z80opts? : {}) {
|
||||
this.probe = new BusProbe(membus);
|
||||
this._cpu = buildZ80(z80opts || {})({
|
||||
display: {},
|
||||
memory: this.probe,
|
||||
ioBus: iobus
|
||||
});
|
||||
return this._cpu;
|
||||
newCPU(membus : MemoryBus, iobus : MemoryBus) {
|
||||
this._cpu = new Z80();
|
||||
this._cpu.connectMemoryBus(membus);
|
||||
this._cpu.connectIOBus(iobus);
|
||||
return this._cpu;
|
||||
}
|
||||
|
||||
getProbe() { return this.probe; } // TODO?
|
||||
getPC() { return this._cpu.getPC(); }
|
||||
getSP() { return this._cpu.getSP(); }
|
||||
isStable() { return true; }
|
||||
|
||||
// TODO: refactor other parts into here
|
||||
runCPU(cpu, cycles:number) {
|
||||
this._cpu = cpu; // TODO?
|
||||
this.waitCycles = 0; // TODO: needs to spill over betwenn calls
|
||||
if (this.wasBreakpointHit())
|
||||
return 0;
|
||||
var debugCond = this.getDebugCallback();
|
||||
var targetTstates = cpu.getTstates() + cycles;
|
||||
try {
|
||||
if (debugCond) { // || trace) {
|
||||
while (cpu.getTstates() < targetTstates) {
|
||||
if (debugCond && debugCond()) {
|
||||
debugCond = null;
|
||||
break;
|
||||
}
|
||||
cpu.runFrame(cpu.getTstates() + 1);
|
||||
}
|
||||
} else {
|
||||
cpu.runFrame(targetTstates);
|
||||
var n = 0;
|
||||
this.waitCycles += cycles;
|
||||
while (this.waitCycles > 0) {
|
||||
if (debugCond && debugCond()) {
|
||||
debugCond = null;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: show alert w/ error msg
|
||||
console.log(e);
|
||||
this.breakpointHit(cpu.getTstates());
|
||||
var cyc = cpu.advanceInsn();
|
||||
n += cyc;
|
||||
this.waitCycles -= cyc;
|
||||
}
|
||||
return cpu.getTstates() - targetTstates;
|
||||
}
|
||||
requestInterrupt(cpu, data) {
|
||||
if (!this.wasBreakpointHit())
|
||||
cpu.requestInterrupt(data);
|
||||
}
|
||||
postFrame() {
|
||||
if (this.debugCallback) {
|
||||
if (this.debugBreakState) {
|
||||
// if breakpoint, reload debug state after frame
|
||||
this.loadState(this.debugBreakState);
|
||||
} else {
|
||||
// reset debug target clocks
|
||||
this.debugSavedState = this.saveState();
|
||||
if (this.debugTargetClock > 0)
|
||||
this.debugTargetClock -= this.debugSavedState.c.T;
|
||||
this.debugSavedState.c.T = 0;
|
||||
this.loadState(this.debugSavedState);
|
||||
}
|
||||
}
|
||||
}
|
||||
breakpointHit(targetClock : number) {
|
||||
this.debugTargetClock = targetClock;
|
||||
this.debugBreakState = this.saveState();
|
||||
console.log("Breakpoint at clk", this.debugBreakState.c.T, "PC", this.debugBreakState.c.PC.toString(16));
|
||||
this.pause();
|
||||
if (this.onBreakpointHit) {
|
||||
this.onBreakpointHit(this.debugBreakState);
|
||||
}
|
||||
}
|
||||
wasBreakpointHit() : boolean {
|
||||
return this.debugBreakState != null;
|
||||
}
|
||||
// TODO: lower bound of clock value
|
||||
step() {
|
||||
this.setDebugCondition( () => {
|
||||
var cpuState = this.getCPUState();
|
||||
if (cpuState.T > this.debugTargetClock) {
|
||||
this.breakpointHit(cpuState.T);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
stepBack() {
|
||||
var prevState;
|
||||
var prevClock;
|
||||
this.setDebugCondition( () => {
|
||||
var cpuState = this.getCPUState();
|
||||
var debugClock = cpuState.T;
|
||||
if (debugClock >= this.debugTargetClock && prevState) {
|
||||
this.loadState(prevState);
|
||||
this.breakpointHit(prevClock);
|
||||
return true;
|
||||
} else if (debugClock > this.debugTargetClock-20 && debugClock < this.debugTargetClock) {
|
||||
prevState = this.saveState();
|
||||
prevClock = debugClock;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
runEval(evalfunc : DebugEvalCondition) {
|
||||
this.setDebugCondition( () => {
|
||||
var cpuState = this.getCPUState();
|
||||
if (cpuState.T > this.debugTargetClock) {
|
||||
if (evalfunc(cpuState)) {
|
||||
this.breakpointHit(cpuState.T);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
runUntilReturn() {
|
||||
var depth = 1;
|
||||
this.runEval( (c) => {
|
||||
if (depth <= 0)
|
||||
return true;
|
||||
var op = this.readAddress(c.PC);
|
||||
if (op == 0xcd) // CALL
|
||||
depth++;
|
||||
else if (op == 0xc0 || op == 0xc8 || op == 0xc9 || op == 0xd0) // RET (TODO?)
|
||||
--depth;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
runToVsync() {
|
||||
this.runEval((c) => { return c['intp']; });
|
||||
return n;
|
||||
}
|
||||
|
||||
getToolForFilename = getToolForFilename_z80;
|
||||
getDefaultExtension() { return ".c"; };
|
||||
// TODO: Z80 opcode metadata
|
||||
|
@ -702,21 +582,6 @@ export abstract class Base6809Platform extends BaseZ80Platform {
|
|||
getPC() { return this._cpu.PC; }
|
||||
getSP() { return this._cpu.SP; }
|
||||
|
||||
runUntilReturn() {
|
||||
var depth = 1;
|
||||
this.runEval((c:CpuState) => {
|
||||
if (depth <= 0)
|
||||
return true;
|
||||
var op = this.readAddress(c.PC);
|
||||
// TODO: 6809 opcodes
|
||||
if (op == 0x9d || op == 0xad || op == 0xbd) // CALL
|
||||
depth++;
|
||||
else if (op == 0x3b || op == 0x39) // RET
|
||||
--depth;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
cpuStateToLongString(c:CpuState) {
|
||||
return cpuStateToLongString_6809(c);
|
||||
}
|
||||
|
@ -1061,127 +926,199 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
|||
var sym = addr2symbol[addr];
|
||||
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
||||
}
|
||||
if (!extra) break;
|
||||
addr--;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
///// Basic Platforms
|
||||
/// new Machine platform adapters
|
||||
|
||||
export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices";
|
||||
import { Probeable, RasterFrameBased, AcceptsPaddleInput } from "./devices";
|
||||
import { SampledAudio } from "./audio";
|
||||
import { ProbeRecorder } from "./recorder";
|
||||
|
||||
cpuFrequency : number;
|
||||
canvasWidth : number;
|
||||
numTotalScanlines : number;
|
||||
numVisibleScanlines : number;
|
||||
defaultROMSize : number;
|
||||
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
||||
}
|
||||
|
||||
cpuCyclesPerLine : number;
|
||||
currentScanline : number;
|
||||
startLineTstates : number;
|
||||
function hasVideo(arg:any): arg is VideoSource {
|
||||
return typeof arg.connectVideo === 'function';
|
||||
}
|
||||
function hasAudio(arg:any): arg is SampledAudioSource {
|
||||
return typeof arg.connectAudio === 'function';
|
||||
}
|
||||
function hasKeyInput(arg:any): arg is AcceptsKeyInput {
|
||||
return typeof arg.setKeyInput === 'function';
|
||||
}
|
||||
function hasPaddleInput(arg:any): arg is AcceptsPaddleInput {
|
||||
return typeof arg.setPaddleInput === 'function';
|
||||
}
|
||||
function isRaster(arg:any): arg is RasterFrameBased {
|
||||
return typeof arg.getRasterY === 'function';
|
||||
}
|
||||
function hasProbe(arg:any): arg is Probeable {
|
||||
return typeof arg.connectProbe == 'function';
|
||||
}
|
||||
|
||||
cpu;
|
||||
membus : MemoryBus;
|
||||
iobus : MemoryBus;
|
||||
ram = new Uint8Array(0);
|
||||
rom = new Uint8Array(0);
|
||||
video;
|
||||
timer;
|
||||
audio;
|
||||
psg;
|
||||
pixels : Uint32Array;
|
||||
inputs : Uint8Array = new Uint8Array(32);
|
||||
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
|
||||
machine : T;
|
||||
mainElement : HTMLElement;
|
||||
timer : AnimationTimer;
|
||||
video : RasterVideo;
|
||||
audio : SampledAudio;
|
||||
poller : ControllerPoller;
|
||||
|
||||
abstract newRAM() : Uint8Array;
|
||||
abstract newMembus() : MemoryBus;
|
||||
abstract newIOBus() : MemoryBus;
|
||||
abstract getVideoOptions() : {};
|
||||
abstract getKeyboardMap();
|
||||
abstract startScanline(sl : number) : void;
|
||||
abstract drawScanline(sl : number) : void;
|
||||
getRasterScanline() : number { return this.currentScanline; }
|
||||
getKeyboardFunction() { return null; }
|
||||
|
||||
probeRecorder : ProbeRecorder;
|
||||
startProbing;
|
||||
stopProbing;
|
||||
|
||||
abstract newMachine() : T;
|
||||
abstract getToolForFilename(s:string) : string;
|
||||
abstract getDefaultExtension() : string;
|
||||
abstract getPresets() : Preset[];
|
||||
|
||||
constructor(mainElement : HTMLElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
this.machine = this.newMachine();
|
||||
}
|
||||
|
||||
reset() { this.machine.reset(); }
|
||||
loadState(s) { this.machine.loadState(s); }
|
||||
saveState() { return this.machine.saveState(); }
|
||||
getSP() { return this.machine.cpu.getSP(); }
|
||||
getPC() { return this.machine.cpu.getPC(); }
|
||||
isStable() { return this.machine.cpu.isStable(); }
|
||||
getCPUState() { return this.machine.cpu.saveState(); }
|
||||
loadControlsState(s) { this.machine.loadControlsState(s); }
|
||||
saveControlsState() { return this.machine.saveControlsState(); }
|
||||
|
||||
start() {
|
||||
this.cpuCyclesPerLine = Math.round(this.cpuFrequency / 60 / this.numTotalScanlines);
|
||||
this.ram = this.newRAM();
|
||||
this.membus = this.newMembus();
|
||||
this.iobus = this.newIOBus();
|
||||
this.cpu = this.newCPU(this.membus, this.iobus);
|
||||
this.video = new RasterVideo(this.mainElement, this.canvasWidth, this.numVisibleScanlines, this.getVideoOptions());
|
||||
this.video.create();
|
||||
this.pixels = this.video.getFrameData();
|
||||
this.poller = setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction());
|
||||
const m = this.machine;
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
readAddress(addr) {
|
||||
return this.membus.read(addr);
|
||||
if (hasVideo(m)) {
|
||||
var vp = m.getVideoParams();
|
||||
this.video = new RasterVideo(this.mainElement, vp.width, vp.height, {overscan:!!vp.overscan,rotate:vp.rotate|0});
|
||||
this.video.create();
|
||||
m.connectVideo(this.video.getFrameData());
|
||||
// TODO: support keyboard w/o video?
|
||||
if (hasKeyInput(m)) {
|
||||
this.video.setKeyboardEvents(m.setKeyInput.bind(m));
|
||||
this.poller = new ControllerPoller(m.setKeyInput.bind(m));
|
||||
}
|
||||
}
|
||||
if (hasAudio(m)) {
|
||||
var ap = m.getAudioParams();
|
||||
this.audio = new SampledAudio(ap.sampleRate);
|
||||
this.audio.start();
|
||||
m.connectAudio(this.audio);
|
||||
}
|
||||
if (hasPaddleInput(m)) {
|
||||
this.video.setupMouseEvents();
|
||||
}
|
||||
if (hasProbe(m)) {
|
||||
this.probeRecorder = new ProbeRecorder(m);
|
||||
this.startProbing = () => {
|
||||
m.connectProbe(this.probeRecorder);
|
||||
return this.probeRecorder;
|
||||
};
|
||||
this.stopProbing = () => {
|
||||
m.connectProbe(null);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
advance(novideo : boolean) {
|
||||
var extraCycles = 0;
|
||||
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
||||
this.startLineTstates = this.cpu.getTstates();
|
||||
this.currentScanline = sl;
|
||||
this.startScanline(sl);
|
||||
extraCycles = this.runCPU(this.cpu, this.cpuCyclesPerLine - extraCycles); // TODO: HALT opcode?
|
||||
this.drawScanline(sl);
|
||||
}
|
||||
this.video.updateFrame();
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
this.rom = padBytes(data, this.defaultROMSize);
|
||||
this.machine.loadROM(data);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.cpu.loadState(state.c);
|
||||
this.ram.set(state.b);
|
||||
this.inputs.set(state.in);
|
||||
pollControls() {
|
||||
this.poller && this.poller.poll();
|
||||
if (hasPaddleInput(this.machine)) {
|
||||
this.machine.setPaddleInput(0, this.video.paddle_x);
|
||||
this.machine.setPaddleInput(1, this.video.paddle_y);
|
||||
}
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:this.ram.slice(0),
|
||||
in:this.inputs.slice(0),
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.in);
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return this.cpu.saveState();
|
||||
|
||||
advance(novideo:boolean) {
|
||||
this.machine.advanceFrame(this.getDebugCallback());
|
||||
if (!novideo && this.video) this.video.updateFrame();
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.timer && this.timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
if (this.audio) this.audio.stop();
|
||||
return this.timer.isRunning();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.timer.start();
|
||||
if (this.audio) this.audio.start();
|
||||
this.audio && this.audio.start();
|
||||
}
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
this.cpu.setTstates(0);
|
||||
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
this.audio && this.audio.stop();
|
||||
}
|
||||
|
||||
// TODO: reset target clock counter
|
||||
getRasterScanline() {
|
||||
return isRaster(this.machine) && this.machine.getRasterY();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move debug info into CPU?
|
||||
|
||||
export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
||||
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
getDebugCategories() {
|
||||
if (isDebuggable(this.machine))
|
||||
return this.machine.getDebugCategories();
|
||||
else
|
||||
return ['CPU','ZPRAM','Stack'];
|
||||
}
|
||||
getDebugInfo(category:string, state:EmuState) : string {
|
||||
switch (category) {
|
||||
case 'CPU': return cpuStateToLongString_6502(state.c);
|
||||
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
||||
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
||||
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseZ80MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
||||
|
||||
//getOpcodeMetadata = getOpcodeMetadata_z80;
|
||||
getToolForFilename = getToolForFilename_z80;
|
||||
|
||||
getDebugCategories() {
|
||||
if (isDebuggable(this.machine))
|
||||
return this.machine.getDebugCategories();
|
||||
else
|
||||
return ['CPU','Stack'];
|
||||
}
|
||||
getDebugInfo(category:string, state:EmuState) : string {
|
||||
switch (category) {
|
||||
case 'CPU': return cpuStateToLongString_Z80(state.c);
|
||||
case 'Stack': {
|
||||
var sp = (state.c.SP-1) & 0xffff;
|
||||
var start = sp & 0xff00;
|
||||
var end = start + 0xff;
|
||||
if (sp == 0) sp = 0x10000;
|
||||
console.log(sp,start,end);
|
||||
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
|
||||
}
|
||||
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,396 @@
|
|||
|
||||
export interface SavesState<S> {
|
||||
saveState() : S;
|
||||
loadState(state:S) : void;
|
||||
}
|
||||
|
||||
export interface Bus {
|
||||
read(a:number) : number;
|
||||
write(a:number, v:number) : void;
|
||||
readConst?(a:number) : number;
|
||||
}
|
||||
|
||||
export interface ClockBased {
|
||||
advanceClock() : void;
|
||||
}
|
||||
|
||||
export interface InstructionBased {
|
||||
advanceInsn() : number;
|
||||
}
|
||||
|
||||
export type TrapCondition = () => boolean;
|
||||
|
||||
export interface FrameBased {
|
||||
advanceFrame(trap:TrapCondition) : number;
|
||||
}
|
||||
|
||||
export interface VideoSource {
|
||||
getVideoParams() : VideoParams;
|
||||
connectVideo(pixels:Uint32Array) : void;
|
||||
}
|
||||
|
||||
export interface RasterFrameBased extends FrameBased, VideoSource {
|
||||
getRasterY() : number;
|
||||
getRasterX() : number;
|
||||
}
|
||||
|
||||
export interface VideoParams {
|
||||
width : number;
|
||||
height : number;
|
||||
overscan? : boolean;
|
||||
rotate? : number;
|
||||
}
|
||||
|
||||
// TODO: frame buffer optimization (apple2, etc)
|
||||
|
||||
export interface SampledAudioParams {
|
||||
sampleRate : number;
|
||||
stereo : boolean;
|
||||
}
|
||||
|
||||
export interface SampledAudioSink {
|
||||
feedSample(value:number, count:number) : void;
|
||||
//sendAudioFrame(samples:Uint16Array) : void;
|
||||
}
|
||||
|
||||
export interface SampledAudioSource {
|
||||
getAudioParams() : SampledAudioParams;
|
||||
connectAudio(audio : SampledAudioSink) : void;
|
||||
}
|
||||
|
||||
export interface AcceptsROM {
|
||||
loadROM(data:Uint8Array, title?:string) : void;
|
||||
}
|
||||
|
||||
export interface Resettable {
|
||||
reset() : void;
|
||||
}
|
||||
|
||||
export interface MemoryBusConnected {
|
||||
connectMemoryBus(bus:Bus) : void;
|
||||
}
|
||||
|
||||
export interface IOBusConnected {
|
||||
connectIOBus(bus:Bus) : void;
|
||||
}
|
||||
|
||||
export interface CPU extends MemoryBusConnected, Resettable, SavesState<any> {
|
||||
getPC() : number;
|
||||
getSP() : number;
|
||||
isStable() : boolean;
|
||||
}
|
||||
|
||||
export interface HasCPU extends Resettable {
|
||||
cpu : CPU;
|
||||
}
|
||||
|
||||
export interface Interruptable<IT> {
|
||||
interrupt(type:IT) : void;
|
||||
}
|
||||
|
||||
export interface SavesInputState<CS> {
|
||||
loadControlsState(cs:CS) : void;
|
||||
saveControlsState() : CS;
|
||||
}
|
||||
|
||||
// TODO: joystick
|
||||
export interface AcceptsKeyInput {
|
||||
setKeyInput(key:number, code:number, flags:number) : void;
|
||||
}
|
||||
|
||||
export interface AcceptsPaddleInput {
|
||||
setPaddleInput(controller:number, value:number) : void;
|
||||
}
|
||||
|
||||
export interface Probeable {
|
||||
connectProbe(probe: ProbeAll) : void;
|
||||
}
|
||||
|
||||
export function xorshift32(x : number) : number {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return x;
|
||||
}
|
||||
|
||||
/// HOOKS
|
||||
|
||||
export interface Hook<T> {
|
||||
//target : T;
|
||||
unhook();
|
||||
}
|
||||
|
||||
export class BusHook implements Hook<Bus> {
|
||||
//target : Bus;
|
||||
constructor(bus : Bus, profiler : ProbeBus) {
|
||||
//this.target = bus;
|
||||
var oldread = bus.read.bind(bus);
|
||||
var oldwrite = bus.write.bind(bus);
|
||||
bus.read = (a:number):number => {
|
||||
var val = oldread(a);
|
||||
profiler.logRead(a,val);
|
||||
return val;
|
||||
}
|
||||
bus.write = (a:number,v:number) => {
|
||||
profiler.logWrite(a,v);
|
||||
oldwrite(a,v);
|
||||
}
|
||||
this.unhook = () => {
|
||||
bus.read = oldread;
|
||||
bus.write = oldwrite;
|
||||
}
|
||||
}
|
||||
unhook : () => void;
|
||||
}
|
||||
|
||||
export class CPUClockHook implements Hook<CPU&ClockBased> {
|
||||
//target : CPU&ClockBased;
|
||||
constructor(cpu : CPU&ClockBased, profiler : ProbeCPU) {
|
||||
//this.target = cpu;
|
||||
var oldclock = cpu.advanceClock.bind(cpu);
|
||||
cpu.advanceClock = () => {
|
||||
profiler.logExecute(cpu.getPC());
|
||||
return oldclock();
|
||||
}
|
||||
this.unhook = () => {
|
||||
cpu.advanceClock = oldclock;
|
||||
}
|
||||
}
|
||||
unhook : () => void;
|
||||
}
|
||||
|
||||
export class CPUInsnHook implements Hook<CPU&InstructionBased> {
|
||||
//target : CPU&InstructionBased;
|
||||
constructor(cpu : CPU&InstructionBased, profiler : ProbeCPU) {
|
||||
//this.target = cpu;
|
||||
var oldinsn = cpu.advanceInsn.bind(cpu);
|
||||
cpu.advanceInsn = () => {
|
||||
profiler.logExecute(cpu.getPC());
|
||||
return oldinsn();
|
||||
}
|
||||
this.unhook = () => {
|
||||
cpu.advanceInsn = oldinsn;
|
||||
}
|
||||
}
|
||||
unhook : () => void;
|
||||
}
|
||||
|
||||
/// PROFILER
|
||||
|
||||
export interface ProbeTime {
|
||||
logClocks(clocks:number);
|
||||
logNewScanline();
|
||||
logNewFrame();
|
||||
}
|
||||
|
||||
export interface ProbeCPU {
|
||||
logExecute(address:number);
|
||||
logInterrupt(type:number);
|
||||
logIllegal(address:number);
|
||||
}
|
||||
|
||||
export interface ProbeBus {
|
||||
logRead(address:number, value:number);
|
||||
logWrite(address:number, value:number);
|
||||
}
|
||||
|
||||
export interface ProbeIO {
|
||||
logIORead(address:number, value:number);
|
||||
logIOWrite(address:number, value:number);
|
||||
}
|
||||
|
||||
export interface ProbeVRAM {
|
||||
logVRAMRead(address:number, value:number);
|
||||
logVRAMWrite(address:number, value:number);
|
||||
}
|
||||
|
||||
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO, ProbeVRAM {
|
||||
}
|
||||
|
||||
export class NullProbe implements ProbeAll {
|
||||
logClocks() {}
|
||||
logNewScanline() {}
|
||||
logNewFrame() {}
|
||||
logExecute() {}
|
||||
logInterrupt() {}
|
||||
logRead() {}
|
||||
logWrite() {}
|
||||
logIORead() {}
|
||||
logIOWrite() {}
|
||||
logVRAMRead() {}
|
||||
logVRAMWrite() {}
|
||||
logIllegal() {}
|
||||
}
|
||||
|
||||
/// CONVENIENCE
|
||||
|
||||
export interface BasicMachineControlsState {
|
||||
inputs: Uint8Array;
|
||||
}
|
||||
|
||||
export interface BasicMachineState extends BasicMachineControlsState {
|
||||
c: any; // TODO
|
||||
ram: Uint8Array;
|
||||
}
|
||||
|
||||
export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, Probeable,
|
||||
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
|
||||
|
||||
abstract cpuFrequency : number;
|
||||
abstract defaultROMSize : number;
|
||||
|
||||
abstract cpu : CPU;
|
||||
abstract ram : Uint8Array;
|
||||
|
||||
rom : Uint8Array;
|
||||
inputs : Uint8Array = new Uint8Array(32);
|
||||
handler : (key,code,flags) => void; // keyboard handler
|
||||
|
||||
nullProbe = new NullProbe();
|
||||
probe : ProbeAll = this.nullProbe;
|
||||
|
||||
abstract read(a:number) : number;
|
||||
abstract write(a:number, v:number) : void;
|
||||
|
||||
setKeyInput(key:number, code:number, flags:number) : void {
|
||||
this.handler && this.handler(key,code,flags);
|
||||
}
|
||||
connectProbe(probe: ProbeAll) : void {
|
||||
this.probe = probe || this.nullProbe;
|
||||
}
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
}
|
||||
loadROM(data:Uint8Array, title?:string) : void {
|
||||
if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize);
|
||||
this.rom.set(data);
|
||||
}
|
||||
loadState(state) {
|
||||
this.cpu.loadState(state.c);
|
||||
this.ram.set(state.ram);
|
||||
this.inputs.set(state.inputs);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.cpu.saveState(),
|
||||
ram:this.ram.slice(0),
|
||||
inputs:this.inputs.slice(0),
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.inputs);
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
inputs:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
advanceCPU() {
|
||||
var c = this.cpu as any;
|
||||
var n = 1;
|
||||
if (this.cpu.isStable()) { this.probe.logExecute(this.cpu.getPC()); }
|
||||
if (c.advanceClock) { c.advanceClock(); }
|
||||
else if (c.advanceInsn) { n = c.advanceInsn(1); }
|
||||
this.probe.logClocks(n);
|
||||
return n;
|
||||
}
|
||||
probeMemoryBus(membus:Bus) : Bus {
|
||||
return {
|
||||
read: (a) => {
|
||||
let val = membus.read(a);
|
||||
this.probe.logRead(a,val);
|
||||
return val;
|
||||
},
|
||||
write: (a,v) => {
|
||||
this.probe.logWrite(a,v);
|
||||
membus.write(a,v);
|
||||
}
|
||||
};
|
||||
}
|
||||
connectCPUMemoryBus(membus:Bus) : void {
|
||||
this.cpu.connectMemoryBus(this.probeMemoryBus(membus));
|
||||
}
|
||||
probeIOBus(iobus:Bus) : Bus {
|
||||
return {
|
||||
read: (a) => {
|
||||
let val = iobus.read(a);
|
||||
this.probe.logIORead(a,val);
|
||||
return val;
|
||||
},
|
||||
write: (a,v) => {
|
||||
this.probe.logIOWrite(a,v);
|
||||
iobus.write(a,v);
|
||||
}
|
||||
};
|
||||
}
|
||||
connectCPUIOBus(iobus:Bus) : void {
|
||||
this.cpu['connectIOBus'](this.probeIOBus(iobus));
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BasicMachine extends BasicHeadlessMachine implements SampledAudioSource {
|
||||
|
||||
abstract canvasWidth : number;
|
||||
abstract numVisibleScanlines : number;
|
||||
abstract sampleRate : number;
|
||||
overscan : boolean = false;
|
||||
rotate : number = 0;
|
||||
|
||||
pixels : Uint32Array;
|
||||
audio : SampledAudioSink;
|
||||
|
||||
scanline : number;
|
||||
|
||||
getAudioParams() : SampledAudioParams {
|
||||
return {sampleRate:this.sampleRate, stereo:false};
|
||||
}
|
||||
connectAudio(audio : SampledAudioSink) : void {
|
||||
this.audio = audio;
|
||||
}
|
||||
getVideoParams() : VideoParams {
|
||||
return {width:this.canvasWidth, height:this.numVisibleScanlines, overscan:this.overscan, rotate:this.rotate};
|
||||
}
|
||||
connectVideo(pixels:Uint32Array) : void {
|
||||
this.pixels = pixels;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased {
|
||||
|
||||
abstract numTotalScanlines : number;
|
||||
abstract cpuCyclesPerLine : number;
|
||||
|
||||
abstract startScanline() : void;
|
||||
abstract drawScanline() : void;
|
||||
|
||||
frameCycles : number;
|
||||
|
||||
advanceFrame(trap: TrapCondition) : number {
|
||||
this.preFrame();
|
||||
var clock = 0;
|
||||
var endLineClock = 0;
|
||||
this.probe.logNewFrame();
|
||||
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
||||
endLineClock += this.cpuCyclesPerLine;
|
||||
this.scanline = sl;
|
||||
this.frameCycles = clock;
|
||||
this.startScanline();
|
||||
while (clock < endLineClock) {
|
||||
if (trap && trap()) {
|
||||
sl = 999;
|
||||
break;
|
||||
}
|
||||
clock += this.advanceCPU();
|
||||
}
|
||||
this.drawScanline();
|
||||
this.probe.logNewScanline();
|
||||
this.probe.logClocks(clock-endLineClock);
|
||||
}
|
||||
this.postFrame();
|
||||
return clock;
|
||||
}
|
||||
preFrame() { }
|
||||
postFrame() { }
|
||||
getRasterY() { return this.scanline; }
|
||||
getRasterX() { return this.frameCycles % this.cpuCyclesPerLine; }
|
||||
}
|
24
src/emu.ts
24
src/emu.ts
|
@ -53,6 +53,9 @@ export enum KeyFlags {
|
|||
export function _setKeyboardEvents(canvas:HTMLElement, callback:KeyboardCallback) {
|
||||
canvas.onkeydown = (e) => {
|
||||
callback(e.which, 0, KeyFlags.KeyDown|_metakeyflags(e));
|
||||
if (e.which == 8 || e.which == 9 || e.which == 27) { // eat backspace, tab, escape keys
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
canvas.onkeyup = (e) => {
|
||||
callback(e.which, 0, KeyFlags.KeyUp|_metakeyflags(e));
|
||||
|
@ -125,7 +128,7 @@ export class RasterVideo {
|
|||
|
||||
getContext() { return this.ctx; }
|
||||
|
||||
updateFrame(sx:number, sy:number, dx:number, dy:number, w?:number, h?:number) {
|
||||
updateFrame(sx?:number, sy?:number, dx?:number, dy?:number, w?:number, h?:number) {
|
||||
if (w && h)
|
||||
this.ctx.putImageData(this.imageData, sx, sy, dx, dy, w, h);
|
||||
else
|
||||
|
@ -248,7 +251,7 @@ export class AnimationTimer {
|
|||
} catch (e) {
|
||||
this.running = false;
|
||||
this.pulsing = false;
|
||||
throw new EmuHalt(e);
|
||||
throw e; // TODO: throw EmuHalt hides stack trace
|
||||
}
|
||||
}
|
||||
if (this.useReqAnimFrame)
|
||||
|
@ -501,7 +504,7 @@ export function newKeyboardHandler(switches:number[]|Uint8Array, map:KeyCodeMap,
|
|||
export function setKeyboardFromMap(video:RasterVideo, switches:number[]|Uint8Array, map:KeyCodeMap, func?:KeyMapFunction, alwaysfunc?:boolean) {
|
||||
var handler = newKeyboardHandler(switches, map, func, alwaysfunc);
|
||||
video.setKeyboardEvents(handler);
|
||||
return new ControllerPoller(map, handler);
|
||||
return new ControllerPoller(handler);
|
||||
}
|
||||
|
||||
export function makeKeycodeMap(table : [KeyDef,number,number][]) : KeyCodeMap {
|
||||
|
@ -514,15 +517,18 @@ export function makeKeycodeMap(table : [KeyDef,number,number][]) : KeyCodeMap {
|
|||
return map;
|
||||
}
|
||||
|
||||
const DEFAULT_CONTROLLER_KEYS : KeyDef[] = [
|
||||
Keys.UP, Keys.DOWN, Keys.LEFT, Keys.RIGHT, Keys.A, Keys.B, Keys.SELECT, Keys.START,
|
||||
Keys.P2_UP, Keys.P2_DOWN, Keys.P2_LEFT, Keys.P2_RIGHT, Keys.P2_A, Keys.P2_B, Keys.P2_SELECT, Keys.P2_START,
|
||||
];
|
||||
|
||||
export class ControllerPoller {
|
||||
active = false;
|
||||
map : KeyCodeMap;
|
||||
handler;
|
||||
state = new Int8Array(32);
|
||||
lastState = new Int8Array(32);
|
||||
AXIS0 = 24; // first joystick axis index
|
||||
constructor(map:KeyCodeMap, handler:(key,code,flags) => void) {
|
||||
this.map = map;
|
||||
constructor(handler:(key,code,flags) => void) {
|
||||
this.handler = handler;
|
||||
window.addEventListener("gamepadconnected", (event) => {
|
||||
console.log("Gamepad connected:", event);
|
||||
|
@ -557,11 +563,11 @@ export class ControllerPoller {
|
|||
}
|
||||
handleStateChange(gpi:number, k:number) {
|
||||
var axis = k - this.AXIS0;
|
||||
for (var code in this.map) {
|
||||
var entry = this.map[code];
|
||||
var def = entry.def;
|
||||
// TODO: this is slow
|
||||
for (var def of DEFAULT_CONTROLLER_KEYS) {
|
||||
// is this a gamepad entry? same player #?
|
||||
if (def && def.plyr == gpi) {
|
||||
var code = def.c;
|
||||
var state = this.state[k];
|
||||
var lastState = this.lastState[k];
|
||||
// check for button/axis match
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,661 @@
|
|||
|
||||
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
|
||||
import { BasicMachine, RasterFrameBased, Bus, ProbeAll } from "../devices";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt, dumpRAM } from "../emu";
|
||||
import { TssChannelAdapter, MasterAudio, POKEYDeviceChannel } from "../audio";
|
||||
import { hex, rgb2bgr } from "../util";
|
||||
|
||||
// https://atarihq.com/danb/a7800.shtml
|
||||
// https://atarihq.com/danb/files/maria_r1.txt
|
||||
// https://sites.google.com/site/atari7800wiki/
|
||||
|
||||
interface Atari7800StateBase {
|
||||
ram : Uint8Array;
|
||||
regs6532 : Uint8Array;
|
||||
}
|
||||
|
||||
interface Atari7800ControlsState {
|
||||
inputs : Uint8Array;
|
||||
}
|
||||
|
||||
interface Atari7800State extends Atari7800StateBase, Atari7800ControlsState {
|
||||
c : MOS6502State;
|
||||
tia : {
|
||||
regs : Uint8Array,
|
||||
};
|
||||
maria : {
|
||||
regs : Uint8Array,
|
||||
offset,dll,dlstart : number;
|
||||
dli,h16,h8 : boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const SWCHA = 0;
|
||||
const SWCHB = 2;
|
||||
const INPT0 = 8;
|
||||
|
||||
const Atari7800_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, INPT0+0, 0x80],
|
||||
[Keys.B, INPT0+1, 0x80],
|
||||
[Keys.SELECT, SWCHB, -0x02],
|
||||
[Keys.START, SWCHB, -0x01],
|
||||
[Keys.UP, SWCHA, -0x10],
|
||||
[Keys.DOWN, SWCHA, -0x20],
|
||||
[Keys.LEFT, SWCHA, -0x40],
|
||||
[Keys.RIGHT, SWCHA, -0x80],
|
||||
|
||||
[Keys.P2_A, INPT0+2, 0x80],
|
||||
[Keys.P2_B, INPT0+3, 0x80],
|
||||
//[Keys.P2_SELECT, 1, 2],
|
||||
//[Keys.P2_START, 1, 3],
|
||||
[Keys.P2_UP, SWCHA, -0x01],
|
||||
[Keys.P2_DOWN, SWCHA, -0x02],
|
||||
[Keys.P2_LEFT, SWCHA, -0x04],
|
||||
[Keys.P2_RIGHT, SWCHA, -0x08],
|
||||
]);
|
||||
|
||||
// http://www.ataripreservation.org/websites/freddy.offenga/megazine/ISSUE5-PALNTSC.html
|
||||
// http://7800.8bitdev.org/index.php/7800_Software_Guide#APPENDIX_4:_FRAME_TIMING
|
||||
const CLK = 3579545;
|
||||
const linesPerFrame = 262;
|
||||
const numVisibleLines = 258-16;
|
||||
const colorClocksPerLine = 454; // 456?
|
||||
const colorClocksPreDMA = 28;
|
||||
const audioOversample = 4;
|
||||
const audioSampleRate = linesPerFrame*60*audioOversample;
|
||||
|
||||
// TIA chip
|
||||
|
||||
class TIA {
|
||||
regs = new Uint8Array(0x20);
|
||||
|
||||
reset() {
|
||||
this.regs.fill(0);
|
||||
}
|
||||
read(a : number) : number {
|
||||
return this.regs[a] | 0;
|
||||
}
|
||||
write(a : number, v : number) {
|
||||
this.regs[a] = v;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
regs: this.regs.slice(0)
|
||||
};
|
||||
}
|
||||
loadState(s) {
|
||||
for (let i=0; i<32; i++)
|
||||
this.write(i, s.regs[i]);
|
||||
}
|
||||
static stateToLongString(state) : string {
|
||||
let s = "";
|
||||
s += dumpRAM(state.regs, 0, 32);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// MARIA chip
|
||||
|
||||
class MARIA {
|
||||
bus : Bus;
|
||||
cycles : number = 0;
|
||||
regs = new Uint8Array(0x20);
|
||||
offset : number = -1;
|
||||
dll : number = 0;
|
||||
dlstart : number = 0;
|
||||
dli : boolean = false;
|
||||
h16 : boolean = false;
|
||||
h8 : boolean = false;
|
||||
pixels = new Uint8Array(320);
|
||||
WSYNC : number = 0;
|
||||
|
||||
reset() {
|
||||
this.regs.fill(0);
|
||||
// TODO?
|
||||
}
|
||||
read(a : number) : number {
|
||||
return this.regs[a] | 0;
|
||||
}
|
||||
write(a : number, v : number) {
|
||||
this.regs[a] = v;
|
||||
if (a == 0x04) this.WSYNC++;
|
||||
//console.log(hex(a), '=', hex(v));
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
regs: this.regs.slice(0),
|
||||
offset: this.offset,
|
||||
dll: this.dll,
|
||||
dlstart: this.dlstart,
|
||||
dli: this.dli,
|
||||
h16: this.h16,
|
||||
h8: this.h8,
|
||||
};
|
||||
}
|
||||
loadState(s) {
|
||||
for (let i=0; i<32; i++)
|
||||
this.write(i, s.regs[i]|0);
|
||||
this.offset = s.offset|0;
|
||||
this.dll = s.dll|0;
|
||||
this.dlstart = s.dlstart|0;
|
||||
this.dli = !!s.dli;
|
||||
this.h16 = !!s.h16;
|
||||
this.h8 = !!s.h8;
|
||||
}
|
||||
isDMAEnabled() {
|
||||
return (this.regs[0x1c] & 0x60) == 0x40;
|
||||
}
|
||||
getDLLStart() {
|
||||
return (this.regs[0x0c] << 8) + this.regs[0x10];
|
||||
}
|
||||
getCharBaseAddress() {
|
||||
return (this.regs[0x14] << 8) + this.offset;
|
||||
}
|
||||
setVBLANK(b : boolean) {
|
||||
if (b) {
|
||||
this.regs[0x08] |= 0x80;
|
||||
this.offset = -1;
|
||||
this.dll = this.getDLLStart();
|
||||
this.dli = this.bus && (this.bus.read(this.dll) & 0x80) != 0; // if DLI on first zone
|
||||
} else {
|
||||
this.regs[0x08] &= ~0x80;
|
||||
}
|
||||
}
|
||||
readDLLEntry(bus) {
|
||||
// display lists must be in RAM (TODO: probe?)
|
||||
if (this.dll >= 0x4000) { return; }
|
||||
let x = bus.read(this.dll);
|
||||
this.offset = (x & 0xf);
|
||||
this.h16 = (x & 0x40) != 0;
|
||||
this.h8 = (x & 0x20) != 0;
|
||||
this.dlstart = (bus.read(this.dll+1)<<8) + bus.read(this.dll+2);
|
||||
//console.log(hex(this.dll,4), this.offset, hex(this.dlstart,4));
|
||||
this.dll = (this.dll + 3) & 0xffff; // TODO: can also only cross 1 page?
|
||||
this.dli = (bus.read(this.dll) & 0x80) != 0; // DLI flag is from next DLL entry
|
||||
}
|
||||
isHoley(a : number) : boolean {
|
||||
if (a & 0x8000) {
|
||||
if (this.h16 && (a & 0x1000)) return true;
|
||||
if (this.h8 && (a & 0x800)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
readDMA(a : number) : number {
|
||||
if (this.isHoley(a))
|
||||
return 0;
|
||||
else {
|
||||
this.cycles += 3;
|
||||
return this.bus.read(a);
|
||||
}
|
||||
}
|
||||
doDMA(bus : Bus) {
|
||||
this.bus = bus;
|
||||
this.cycles = 0;
|
||||
this.pixels.fill(this.regs[0x0]);
|
||||
if (this.isDMAEnabled()) {
|
||||
this.cycles += 16; // TODO: last line in zone gets additional 8 cycles
|
||||
// time for a new DLL entry?
|
||||
if (this.offset < 0) {
|
||||
this.readDLLEntry(bus);
|
||||
}
|
||||
// read the DL (only can span two pages)
|
||||
let dlhi = this.dlstart & 0xff00;
|
||||
let dlofs = this.dlstart & 0xff;
|
||||
do {
|
||||
// read DL entry
|
||||
let b0 = bus.read(dlhi + ((dlofs+0) & 0x1ff));
|
||||
let b1 = bus.read(dlhi + ((dlofs+1) & 0x1ff));
|
||||
if (b1 == 0) break; // end of DL
|
||||
// display lists must be in RAM (TODO: probe?)
|
||||
if (dlhi >= 0x4000) { break; }
|
||||
let b2 = bus.read(dlhi + ((dlofs+2) & 0x1ff));
|
||||
let b3 = bus.read(dlhi + ((dlofs+3) & 0x1ff));
|
||||
let indirect = false;
|
||||
// extended header?
|
||||
if ((b1 & 31) == 0) {
|
||||
var pal = b3 >> 5;
|
||||
var width = 32 - (b3 & 31);
|
||||
var xpos = bus.read(dlhi + ((dlofs+4) & 0x1ff));
|
||||
var writemode = b1 & 0x80;
|
||||
indirect = (b1 & 0x20) != 0;
|
||||
dlofs += 5;
|
||||
this.cycles += 10;
|
||||
} else {
|
||||
// direct mode
|
||||
var xpos = b3;
|
||||
var pal = b1 >> 5;
|
||||
var width = 32 - (b1 & 31);
|
||||
var writemode = 0;
|
||||
dlofs += 4;
|
||||
this.cycles += 8;
|
||||
}
|
||||
let gfxadr = b0 + (((b2 + (indirect?0:this.offset)) & 0xff) << 8);
|
||||
xpos *= 2;
|
||||
// copy graphics data (direct)
|
||||
let readmode = (this.regs[0x1c] & 0x3) + (writemode?4:0);
|
||||
// double bytes?
|
||||
let dbl = indirect && (this.regs[0x1c] & 0x10) != 0;
|
||||
if (dbl) { width *= 2; }
|
||||
//if (this.offset == 0) console.log(hex(dla,4), hex(gfxadr,4), xpos, width, pal, readmode);
|
||||
for (var i=0; i<width; i++) {
|
||||
let data = this.readDMA( dbl ? (gfxadr+(i>>1)) : (gfxadr+i) );
|
||||
if (indirect) {
|
||||
let indadr = ((this.regs[0x14] + this.offset) << 8) + data;
|
||||
if (dbl && (i&1)) {
|
||||
indadr++;
|
||||
this.cycles -= 3; // indirect read has 6/9 cycles
|
||||
}
|
||||
data = this.readDMA(indadr);
|
||||
}
|
||||
// TODO: more modes
|
||||
switch (readmode) {
|
||||
case 0: // 160 A/B
|
||||
for (let j=0; j<4; j++) {
|
||||
var col = (data >> 6) & 3;
|
||||
if (col > 0) {
|
||||
this.pixels[xpos] = this.pixels[xpos+1] = this.regs[(pal<<2) + col];
|
||||
}
|
||||
data <<= 2;
|
||||
xpos = (xpos + 2) & 0x1ff;
|
||||
}
|
||||
break;
|
||||
case 2: // 320 B/D (TODO?)
|
||||
case 3: // 320 A/C
|
||||
for (let j=0; j<8; j++) {
|
||||
var col = (data & 128) ? 1 : 0;
|
||||
if (col > 0) {
|
||||
this.pixels[xpos] = this.regs[(pal<<2) + col];
|
||||
}
|
||||
data <<= 1;
|
||||
xpos = (xpos + 1) & 0x1ff;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (this.cycles < colorClocksPerLine); // TODO?
|
||||
// decrement offset
|
||||
this.offset -= 1;
|
||||
}
|
||||
return this.cycles;
|
||||
}
|
||||
doInterrupt() : boolean {
|
||||
if (this.dli && this.offset < 0) {
|
||||
this.dli = false;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
//return this.dli;// && this.offset == 1;
|
||||
}
|
||||
static stateToLongString(state) : string {
|
||||
let s = "";
|
||||
s += dumpRAM(state.regs, 0, 32);
|
||||
s += "\n DLL: $" + hex((state.regs[0x0c] << 8) + state.regs[0x10],4) + " @ $" + hex(state.dll,4);
|
||||
s += "\n DL: $" + hex(state.dlstart,4);
|
||||
s += "\nOffset: " + state.offset;
|
||||
s += "\n DLI? " + state.dli;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Atari 7800
|
||||
|
||||
export class Atari7800 extends BasicMachine implements RasterFrameBased {
|
||||
|
||||
cpuFrequency = 1789772;
|
||||
canvasWidth = 320;
|
||||
numTotalScanlines = linesPerFrame;
|
||||
numVisibleScanlines = numVisibleLines;
|
||||
defaultROMSize = 0xc000;
|
||||
cpuCyclesPerLine = 113.5;
|
||||
sampleRate = audioSampleRate;
|
||||
|
||||
cpu : MOS6502;
|
||||
ram : Uint8Array = new Uint8Array(0x1000);
|
||||
regs6532 = new Uint8Array(4);
|
||||
tia : TIA = new TIA();
|
||||
maria : MARIA = new MARIA();
|
||||
pokey1; //TODO: type
|
||||
audioadapter;
|
||||
|
||||
lastFrameCycles = 0;
|
||||
xtracyc = 0;
|
||||
|
||||
read : (a:number) => number;
|
||||
write : (a:number, v:number) => void;
|
||||
|
||||
probeDMABus : Bus; // to pass to MARIA
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.cpu = new MOS6502();
|
||||
this.read = newAddressDecoder([
|
||||
[0x0008, 0x000d, 0x0f, (a) => { this.xtracyc++; return this.readInput(a); }],
|
||||
[0x0000, 0x001f, 0x1f, (a) => { this.xtracyc++; return this.tia.read(a); }],
|
||||
[0x0020, 0x003f, 0x1f, (a) => { return this.maria.read(a); }],
|
||||
[0x0040, 0x00ff, 0xff, (a) => { return this.ram[a + 0x800]; }],
|
||||
[0x0100, 0x013f, 0xff, (a) => { return this.read(a); }], // shadow
|
||||
[0x0140, 0x01ff, 0x1ff, (a) => { return this.ram[a + 0x800]; }],
|
||||
[0x0280, 0x02ff, 0x3, (a) => { this.xtracyc++; return this.inputs[a]; }],
|
||||
[0x1800, 0x27ff, 0xffff, (a) => { return this.ram[a - 0x1800]; }],
|
||||
[0x2800, 0x3fff, 0x7ff, (a) => { return this.read(a | 0x2000); }], // shadow
|
||||
[0x4000, 0xffff, 0xffff, (a) => { return this.rom ? this.rom[a - 0x4000] : 0; }],
|
||||
[0x0000, 0xffff, 0xffff, (a) => { return this.probe && this.probe.logIllegal(a); }],
|
||||
]);
|
||||
this.write = newAddressDecoder([
|
||||
[0x0015, 0x001A, 0x1f, (a,v) => { this.xtracyc++; this.pokey1.setTIARegister(a, v); }],
|
||||
[0x0000, 0x001f, 0x1f, (a,v) => { this.xtracyc++; this.tia.write(a,v); }],
|
||||
[0x0020, 0x003f, 0x1f, (a,v) => { this.maria.write(a,v); }],
|
||||
[0x0040, 0x00ff, 0xff, (a,v) => { this.ram[a + 0x800] = v; }],
|
||||
[0x0100, 0x013f, 0xff, (a,v) => { this.write(a,v); }], // shadow
|
||||
[0x0140, 0x01ff, 0x1ff, (a,v) => { this.ram[a + 0x800] = v; }],
|
||||
[0x0280, 0x02ff, 0x3, (a,v) => { this.xtracyc++; this.regs6532[a] = v; /*TODO*/ }],
|
||||
[0x1800, 0x27ff, 0xffff, (a,v) => { this.ram[a - 0x1800] = v; }],
|
||||
[0x2800, 0x3fff, 0x7ff, (a,v) => { this.write(a | 0x2000, v); }], // shadow
|
||||
[0xbfff, 0xbfff, 0xffff, (a,v) => { }], // TODO: bank switching?
|
||||
[0x0000, 0xffff, 0xffff, (a,v) => { this.probe && this.probe.logIllegal(a); }],
|
||||
]);
|
||||
this.connectCPUMemoryBus(this);
|
||||
this.probeDMABus = this.probeIOBus(this);
|
||||
this.handler = newKeyboardHandler(this.inputs, Atari7800_KEYCODE_MAP);
|
||||
this.pokey1 = new POKEYDeviceChannel();
|
||||
this.audioadapter = new TssChannelAdapter(this.pokey1, audioOversample, audioSampleRate);
|
||||
}
|
||||
|
||||
readConst(a) {
|
||||
// make sure we don't log during this
|
||||
let oldprobe = this.probe;
|
||||
this.probe = null;
|
||||
let v = this.read(a);
|
||||
this.probe = oldprobe;
|
||||
return v;
|
||||
}
|
||||
|
||||
readInput(a:number) : number {
|
||||
switch (a) {
|
||||
case 0xc: return ~this.inputs[0x8] & 0x80; //INPT4
|
||||
case 0xd: return ~this.inputs[0x9] & 0x80; //INPT5
|
||||
default: return this.inputs[a]|0;
|
||||
}
|
||||
}
|
||||
|
||||
advanceCPU() : number {
|
||||
var clk = super.advanceCPU();
|
||||
if (this.xtracyc) {
|
||||
clk += this.xtracyc;
|
||||
this.probe.logClocks(this.xtracyc);
|
||||
this.xtracyc = 0;
|
||||
}
|
||||
return clk;
|
||||
}
|
||||
|
||||
advanceFrame(trap) : number {
|
||||
var idata = this.pixels;
|
||||
var iofs = 0;
|
||||
var rgb;
|
||||
var mc = 0;
|
||||
var fc = 0;
|
||||
this.probe.logNewFrame();
|
||||
//console.log(hex(this.cpu.getPC()), hex(this.maria.dll));
|
||||
// visible lines
|
||||
for (var sl=0; sl<linesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
var visible = sl < numVisibleLines;
|
||||
this.maria.setVBLANK(!visible);
|
||||
this.maria.WSYNC = 0;
|
||||
// pre-DMA clocks
|
||||
while (mc < colorClocksPreDMA) {
|
||||
if (this.maria.WSYNC) break;
|
||||
if (trap && trap()) {
|
||||
trap = null;
|
||||
sl = 999;
|
||||
break; // TODO?
|
||||
}
|
||||
mc += this.advanceCPU() << 2;
|
||||
}
|
||||
// is this scanline visible?
|
||||
if (visible) {
|
||||
// do DMA for scanline?
|
||||
let dmaClocks = this.maria.doDMA(this.probeDMABus);
|
||||
this.probe.logClocks(dmaClocks >> 2); // TODO: logDMA
|
||||
mc += dmaClocks;
|
||||
// copy line to frame buffer
|
||||
if (idata) {
|
||||
for (var i=0; i<320; i++) {
|
||||
idata[iofs++] = COLORS_RGBA[this.maria.pixels[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
// do interrupt? (if visible or before 1st scanline)
|
||||
if ((visible || sl == linesPerFrame-1) && this.maria.doInterrupt()) {
|
||||
this.probe.logInterrupt(0);
|
||||
this.cpu.NMI();
|
||||
}
|
||||
// post-DMA clocks
|
||||
while (mc < colorClocksPerLine) {
|
||||
if (this.maria.WSYNC) {
|
||||
this.probe.logClocks((colorClocksPerLine - mc) >> 2);
|
||||
mc = colorClocksPerLine;
|
||||
break;
|
||||
}
|
||||
if (trap && trap()) {
|
||||
trap = null;
|
||||
sl = 999;
|
||||
break;
|
||||
}
|
||||
mc += this.advanceCPU() << 2;
|
||||
}
|
||||
// audio
|
||||
this.audio && this.audioadapter.generate(this.audio);
|
||||
// update clocks, scanline
|
||||
mc -= colorClocksPerLine;
|
||||
fc += mc;
|
||||
this.probe.logNewScanline();
|
||||
}
|
||||
/*
|
||||
// TODO let bkcol = this.maria.regs[0x0];
|
||||
// TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
|
||||
*/
|
||||
return (this.lastFrameCycles = fc);
|
||||
}
|
||||
|
||||
getRasterX() { return this.lastFrameCycles % colorClocksPerLine; }
|
||||
getRasterY() { return Math.floor(this.lastFrameCycles / colorClocksPerLine); }
|
||||
|
||||
loadROM(data) {
|
||||
if (data.length == 0xc080) data = data.slice(0x80); // strip header
|
||||
this.rom = padBytes(data, this.defaultROMSize, true);
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.tia.reset();
|
||||
this.maria.reset();
|
||||
this.inputs.fill(0x0);
|
||||
this.inputs[SWCHA] = 0xff;
|
||||
this.inputs[SWCHB] = 1+2+8;
|
||||
//this.cpu.advanceClock(); // needed for test to pass?
|
||||
}
|
||||
|
||||
readAddress(addr : number) {
|
||||
return this.read(addr) | 0;
|
||||
}
|
||||
|
||||
loadState(state : Atari7800State) {
|
||||
this.cpu.loadState(state.c);
|
||||
this.ram.set(state.ram);
|
||||
this.tia.loadState(state.tia);
|
||||
this.maria.loadState(state.maria);
|
||||
this.regs6532.set(state.regs6532);
|
||||
this.loadControlsState(state);
|
||||
}
|
||||
saveState() : Atari7800State {
|
||||
return {
|
||||
c:this.cpu.saveState(),
|
||||
ram:this.ram.slice(0),
|
||||
tia:this.tia.saveState(),
|
||||
maria:this.maria.saveState(),
|
||||
regs6532:this.regs6532.slice(0),
|
||||
inputs:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
loadControlsState(state:Atari7800ControlsState) : void {
|
||||
this.inputs.set(state.inputs);
|
||||
}
|
||||
saveControlsState() : Atari7800ControlsState {
|
||||
return {
|
||||
inputs:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return ['CPU','Stack','TIA','MARIA'];
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'TIA': return TIA.stateToLongString(state.tia);
|
||||
case 'MARIA': return MARIA.stateToLongString(state.maria) + "\nScanline: " + this.scanline;
|
||||
//default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
const ATARI_NTSC_RGB = [
|
||||
0x000000, // 00
|
||||
0x404040, // 02
|
||||
0x6c6c6c, // 04
|
||||
0x909090, // 06
|
||||
0xb0b0b0, // 08
|
||||
0xc8c8c8, // 0A
|
||||
0xdcdcdc, // 0C
|
||||
0xf4f4f4, // 0E
|
||||
0x004444, // 10
|
||||
0x106464, // 12
|
||||
0x248484, // 14
|
||||
0x34a0a0, // 16
|
||||
0x40b8b8, // 18
|
||||
0x50d0d0, // 1A
|
||||
0x5ce8e8, // 1C
|
||||
0x68fcfc, // 1E
|
||||
0x002870, // 20
|
||||
0x144484, // 22
|
||||
0x285c98, // 24
|
||||
0x3c78ac, // 26
|
||||
0x4c8cbc, // 28
|
||||
0x5ca0cc, // 2A
|
||||
0x68b4dc, // 2C
|
||||
0x78c8ec, // 2E
|
||||
0x001884, // 30
|
||||
0x183498, // 32
|
||||
0x3050ac, // 34
|
||||
0x4868c0, // 36
|
||||
0x5c80d0, // 38
|
||||
0x7094e0, // 3A
|
||||
0x80a8ec, // 3C
|
||||
0x94bcfc, // 3E
|
||||
0x000088, // 40
|
||||
0x20209c, // 42
|
||||
0x3c3cb0, // 44
|
||||
0x5858c0, // 46
|
||||
0x7070d0, // 48
|
||||
0x8888e0, // 4A
|
||||
0xa0a0ec, // 4C
|
||||
0xb4b4fc, // 4E
|
||||
0x5c0078, // 50
|
||||
0x74208c, // 52
|
||||
0x883ca0, // 54
|
||||
0x9c58b0, // 56
|
||||
0xb070c0, // 58
|
||||
0xc084d0, // 5A
|
||||
0xd09cdc, // 5C
|
||||
0xe0b0ec, // 5E
|
||||
0x780048, // 60
|
||||
0x902060, // 62
|
||||
0xa43c78, // 64
|
||||
0xb8588c, // 66
|
||||
0xcc70a0, // 68
|
||||
0xdc84b4, // 6A
|
||||
0xec9cc4, // 6C
|
||||
0xfcb0d4, // 6E
|
||||
0x840014, // 70
|
||||
0x982030, // 72
|
||||
0xac3c4c, // 74
|
||||
0xc05868, // 76
|
||||
0xd0707c, // 78
|
||||
0xe08894, // 7A
|
||||
0xeca0a8, // 7C
|
||||
0xfcb4bc, // 7E
|
||||
0x880000, // 80
|
||||
0x9c201c, // 82
|
||||
0xb04038, // 84
|
||||
0xc05c50, // 86
|
||||
0xd07468, // 88
|
||||
0xe08c7c, // 8A
|
||||
0xeca490, // 8C
|
||||
0xfcb8a4, // 8E
|
||||
0x7c1800, // 90
|
||||
0x90381c, // 92
|
||||
0xa85438, // 94
|
||||
0xbc7050, // 96
|
||||
0xcc8868, // 98
|
||||
0xdc9c7c, // 9A
|
||||
0xecb490, // 9C
|
||||
0xfcc8a4, // 9E
|
||||
0x5c2c00, // A0
|
||||
0x784c1c, // A2
|
||||
0x906838, // A4
|
||||
0xac8450, // A6
|
||||
0xc09c68, // A8
|
||||
0xd4b47c, // AA
|
||||
0xe8cc90, // AC
|
||||
0xfce0a4, // AE
|
||||
0x2c3c00, // B0
|
||||
0x485c1c, // B2
|
||||
0x647c38, // B4
|
||||
0x809c50, // B6
|
||||
0x94b468, // B8
|
||||
0xacd07c, // BA
|
||||
0xc0e490, // BC
|
||||
0xd4fca4, // BE
|
||||
0x003c00, // C0
|
||||
0x205c20, // C2
|
||||
0x407c40, // C4
|
||||
0x5c9c5c, // C6
|
||||
0x74b474, // C8
|
||||
0x8cd08c, // CA
|
||||
0xa4e4a4, // CC
|
||||
0xb8fcb8, // CE
|
||||
0x003814, // D0
|
||||
0x1c5c34, // D2
|
||||
0x387c50, // D4
|
||||
0x50986c, // D6
|
||||
0x68b484, // D8
|
||||
0x7ccc9c, // DA
|
||||
0x90e4b4, // DC
|
||||
0xa4fcc8, // DE
|
||||
0x00302c, // E0
|
||||
0x1c504c, // E2
|
||||
0x347068, // E4
|
||||
0x4c8c84, // E6
|
||||
0x64a89c, // E8
|
||||
0x78c0b4, // EA
|
||||
0x88d4cc, // EC
|
||||
0x9cece0, // EE
|
||||
0x002844, // F0
|
||||
0x184864, // F2
|
||||
0x306884, // F4
|
||||
0x4484a0, // F6
|
||||
0x589cb8, // F8
|
||||
0x6cb4d0, // FA
|
||||
0x7ccce8, // FC
|
||||
0x8ce0fc // FE
|
||||
];
|
||||
|
||||
var COLORS_RGBA = new Uint32Array(256);
|
||||
var COLORS_WEB = [];
|
||||
for (var i=0; i<256; i++) {
|
||||
COLORS_RGBA[i] = ATARI_NTSC_RGB[i>>1] | 0xff000000;
|
||||
COLORS_WEB[i] = "#"+hex(rgb2bgr(ATARI_NTSC_RGB[i>>1]),6);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,160 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine } from "../devices";
|
||||
import { BaseZ80VDPBasedMachine } from "./vdp_z80";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { TssChannelAdapter, MasterAudio, SN76489_Audio } from "../audio";
|
||||
import { TMS9918A } from "../video/tms9918a";
|
||||
|
||||
// http://www.colecovision.eu/ColecoVision/development/tutorial1.shtml
|
||||
// http://www.colecovision.eu/ColecoVision/development/libcv.shtml
|
||||
// http://www.kernelcrash.com/blog/recreating-the-colecovision/2016/01/27/
|
||||
// http://www.atarihq.com/danb/files/CV-Tech.txt
|
||||
// http://www.atarihq.com/danb/files/CV-Sound.txt
|
||||
// http://www.colecoboxart.com/faq/FAQ05.htm
|
||||
// http://www.theadamresource.com/manuals/technical/Jeffcoleco.html
|
||||
// http://bifi.msxnet.org/msxnet//tech/tms9918a.txt
|
||||
// http://www.colecovision.dk/tools.htm?refreshed
|
||||
// http://www.theadamresource.com/manuals/technical/ColecoVision%20Coding%20Guide.pdf
|
||||
// http://www.unige.ch/medecine/nouspikel/ti99/tms9918a.htm
|
||||
// http://map.grauw.nl/articles/vdp_tut.php
|
||||
// http://www.msxcomputermagazine.nl/mccw/91/msx1demos1/en.html
|
||||
// http://www.segordon.com/colecovision.php
|
||||
// http://samdal.com/svvideo.htm
|
||||
// https://github.com/tursilion/convert9918
|
||||
// http://www.harmlesslion.com/cgi-bin/showprog.cgi?ColecoVision
|
||||
|
||||
var COLECOVISION_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x4],
|
||||
[Keys.LEFT, 0, 0x8],
|
||||
[Keys.RIGHT, 0, 0x2],
|
||||
[Keys.A, 0, 0x40],
|
||||
[Keys.B, 1, 0x40],
|
||||
|
||||
[Keys.P2_UP, 2, 0x1],
|
||||
[Keys.P2_DOWN, 2, 0x4],
|
||||
[Keys.P2_LEFT, 2, 0x8],
|
||||
[Keys.P2_RIGHT, 2, 0x2],
|
||||
[Keys.P2_A, 2, 0x40],
|
||||
[Keys.P2_B, 3, 0x40],
|
||||
]);
|
||||
|
||||
export class ColecoVision extends BaseZ80VDPBasedMachine {
|
||||
|
||||
defaultROMSize = 0x8000;
|
||||
ram = new Uint8Array(0x400);
|
||||
bios: Uint8Array;
|
||||
keypadMode: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.init(this, this.newIOBus(), new SN76489_Audio(new MasterAudio()));
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(COLECO_BIOS_LZG)));
|
||||
}
|
||||
|
||||
getKeyboardMap() { return COLECOVISION_KEYCODE_MAP; }
|
||||
|
||||
vdpInterrupt() {
|
||||
this.probe.logInterrupt(0);
|
||||
this.cpu.NMI();
|
||||
}
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0x0000, 0x1fff, 0x1fff, (a) => { return this.bios ? this.bios[a] : 0; }],
|
||||
[0x6000, 0x7fff, 0x03ff, (a) => { return this.ram[a]; }],
|
||||
[0x8000, 0xffff, 0x7fff, (a) => { return this.rom ? this.rom[a] : 0; }],
|
||||
]);
|
||||
|
||||
write = newAddressDecoder([
|
||||
[0x6000, 0x7fff, 0x03ff, (a, v) => { this.ram[a] = v; }],
|
||||
]);
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr:number):number => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0xfc: return this.inputs[this.keypadMode ? 1 : 0] ^ 0xff;
|
||||
case 0xff: return this.inputs[this.keypadMode ? 3 : 2] ^ 0xff;
|
||||
}
|
||||
if (addr >= 0xa0 && addr <= 0xbf) {
|
||||
if (addr & 1)
|
||||
return this.vdp.readStatus();
|
||||
else
|
||||
return this.vdp.readData();
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr >> 4) {
|
||||
case 0x8: case 0x9: this.keypadMode = true; break;
|
||||
case 0xc: case 0xd: this.keypadMode = false; break;
|
||||
case 0xa: case 0xb:
|
||||
if (addr & 1)
|
||||
return this.vdp.writeAddress(val);
|
||||
else
|
||||
return this.vdp.writeData(val);
|
||||
case 0xf: this.psg.setData(val); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.keypadMode = state['kpm'];
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['kpm'] = this.keypadMode;
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.keypadMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
var COLECO_BIOS_LZG = `
|
||||
TFpHAAAgAAAAB7djQcnHAQEDBgcx/3MYawAAAMMMgAehB+EPB+USB+UVB+UYB+UbB+UeB+QHHAZm
|
||||
IYA8igUCBYIAKgCAff5VIAl8/qogBCoKgOnHAwkfgICAAAMFT6CgB4LgByEH4WDAYMBABlggQIAg
|
||||
B+HAwOCgYAMGKweBQAYxBphAQEAG+KBABnAGEuAGUAabB+QA4AflBkggIAYyB+FgoKCgwAZdwAY5
|
||||
B+HABhAGYAfhIAZQoKDgIAMCcOCAwAaIYIDgoAZY4AMFOAYGBsgGIAZYAwJWBlAAQAMEcAYfQAZ4
|
||||
BhoDA3gGBgaQBjgGGwYfoOCAAwLIB+EDA/DAoAchAwNggAaQwAMFmOCAByEH5QZ4AwKABligAwUw
|
||||
4EBAQAZYICAgoAMCUAYuBpAGPwawoOAG4AfhAyNQQKCgBqDAoMADJECgoOADBGjgwAZYYIADBPgD
|
||||
AlADBEigAwVooAMFCAMEQAYHAwRQBg8GUAMiEAMEoAMEcAMD6gaQICAGyAADJZgDJ4gDI7gAwGAD
|
||||
I0CAwAME6AMC+QMCaCBgAwRwAGCgwAaIQOADA2AGJiAH4gZoAwPgA0M6ACAAAwX4gKDAAwPAAyLP
|
||||
AwNgAwXwAyNJAwR+AwX4oMADRQBgAyOgAyJxB+PAYANDEOBAQAMDeAMF+AfhA0RQAyJBB+NAAwPI
|
||||
BncGkOBgwAZQYECABrADQs8DAkjAQCBAA0QYAAMDUAcLOERsRFREOAA4fFR8RHw4AAAofHx8OBAA
|
||||
ABA4B+MQODgQfHwDBQgQBhgAADAwB4H8/PzMzAeBAAB4SEh4BkiEtLSEBggcDDRISDAAOEREOBAG
|
||||
eBgUEDBwYAAMNCw0LGxgAABUOGw4VAAAIDA4PDgwIAAIGDh4OBgIAwJXEAMCaCgHAgAoADxUVDQU
|
||||
FBQAOEQwKBhEAwNwAAB4eAMGIAchfBAHAQAHggMEqBh8GAfBABAwfDAH4gAAQEBAfAAAKCh8KCgG
|
||||
DxA4OHwGB3x8ODgGLwcHBg8GfgBsbEgGyQZnfCgAIDhAMAhwEABkZAgQIExMACBQUCBUSDQAMDAD
|
||||
ZEsQIAcCEAAgAwJ5EAYMKDh8OCgGURB8AwdYBuV8AwdlAyIyBAgQAyKmOERMVGREOAAQMAZ4OAYI
|
||||
BBggQHwH4jgEBhAIGChIfAgIAHxAQHgGSAYVeEQH4XwGeCAgAyNYBkgHgTwECAMiijAwAySSB+Mg
|
||||
Bh5AIBAIAwR3B0EGBAZyAwNgAwLoOERcVFxABrhEfEREAHhERAdCBghAQEBEOAZIBwF4AwN4AyJA
|
||||
B+RABlhcREQ8AAbvRAA4AwP4OAAEBwEDAohESFBgUEhEAyJ1AyN4RGxUBh9EAERkVEwGqAZGAwRY
|
||||
AwVIREQDIlAGSEgGmEADA/gDI9YQEAMCYAaoB8MoBkhUBwEoB+EoECgGKAaOBiB4AwLpQHgAOAMj
|
||||
kDgAAAMC9gQAADgIBwI4AAYlA0rv/DADRR0GGgQ8RDwABjQDA+gGCEQDAvgEBg4DAuAGSHhAAyJ4
|
||||
IHgDInAAAAYQPAQ4QEBwSAcBAANEehgACAAYCAgISDBAQAMD+QaOBlAAAGhUVEQDAnQG6AMCSAMC
|
||||
yAADI1p4QAMDSEQ8BAAAWCQgIHAGWEA4BAYYAwJnKAMCnAYuWANCUQME+AfiVHwGSEhIMAMDSAYY
|
||||
OBBgAAB4CDBAeAMCoGAgIBgDAngHYzAICAwICDAAKFADRuhsRER8AwPeQEQ4EDBIAwVYDAMF4Ach
|
||||
AyMIKAflMAflOCgH5AMkFxAwBiAGqAYgB+MGIAMFCAME8BAGgRgAIAMFCAYHKER8RAMCQGwH4gwA
|
||||
fEB4A2KYAHgUfFA8ADxQUHxQUFwAOAAwSAOCOCgH5WAH5TgDBaBgB+UoAwXwAwL9BugH5AAQOEBA
|
||||
OBAAGCQgeCAkXABEKBB8ByEAYFBQaFxISAAIFBA4EBBQIBgDBdADIyIQGAfhAwRgGAMGWFADI6kH
|
||||
4khoWEgDBSY8AwUeeAMi8DADQiAAA2ISA6N2/AQEB6FASFA4RAgcB+IsVBwEBmADglsGEiRIJAZf
|
||||
AEgkAyKuVACoB2SoBySo/FT8B2IDRDAHA/AHpQfjUFBQ0AdhAyK+8AfjAwYQ0BAGkAcGBhAGyNAQ
|
||||
8AYkAAYCB+MGJgfjBngDBVgcBpAQEPwDZgwDBhAHggZIAwYYAwUOBpBQUFBcAwNYXAMjtQMC9wbI
|
||||
3AboB6HcAwUYBpAGCAaQBhgGkBADBghQUAMGKAMDWAZCAwW7AwNQAwJoBpgDBXAGAnwG11ADB0AD
|
||||
JiADBbgGoPwHBQZJB+LgBwUcBwUDBhQAADRISDQDY49wSEhwQHhIA4OpAAB8A8R6eEggECADwtEA
|
||||
PANDTwNjYHAGWShQAwKHOBA4A8LZA0IIeAaZOEREKChsADBAIBA4BqkoA4KfA0KIVFQDIvg4QANi
|
||||
FwAGaQNjr3gHIwZYEAAGEEAwCDBABgkHoTAIBlEIFANIAlAgBiAAfAdhAwJ3B0IGfwMDjgPmiAcB
|
||||
A8SRHAYnBihQAwK9BwFgEAODTQPjNXgDAmgHHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8H
|
||||
HwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBx8HHwcfBwM=`;
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
|
||||
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
|
||||
import { BasicHeadlessMachine } from "../devices";
|
||||
import { padBytes, Keys, KeyFlags, newAddressDecoder } from "../emu"; // TODO
|
||||
import { hex, stringToByteArray, lzgmini } from "../util";
|
||||
|
||||
const KIM1_KEYMATRIX_NOSHIFT = [
|
||||
Keys.VK_DELETE, Keys.VK_ENTER, Keys.VK_RIGHT, Keys.VK_F7, Keys.VK_F1, Keys.VK_F3, Keys.VK_F5, Keys.VK_DOWN,
|
||||
Keys.VK_3, Keys.VK_W, Keys.VK_A, Keys.VK_4, Keys.VK_Z, Keys.VK_S, Keys.VK_E, Keys.VK_SHIFT,
|
||||
Keys.VK_5, Keys.VK_R, Keys.VK_D, Keys.VK_6, Keys.VK_C, Keys.VK_F, Keys.VK_T, Keys.VK_X,
|
||||
Keys.VK_7, Keys.VK_Y, Keys.VK_G, Keys.VK_8, Keys.VK_B, Keys.VK_H, Keys.VK_U, Keys.VK_V,
|
||||
Keys.VK_9, Keys.VK_I, Keys.VK_J, Keys.VK_0, Keys.VK_M, Keys.VK_K, Keys.VK_O, Keys.VK_N,
|
||||
null/*Keys.VK_PLUS*/, Keys.VK_P, Keys.VK_L, Keys.VK_MINUS, Keys.VK_PERIOD, null/*Keys.VK_COLON*/, null/*Keys.VK_AT*/, Keys.VK_COMMA,
|
||||
null/*Keys.VK_POUND*/, null/*TIMES*/, Keys.VK_SEMICOLON, Keys.VK_HOME, Keys.VK_SHIFT/*right*/, Keys.VK_EQUALS, Keys.VK_TILDE, Keys.VK_SLASH,
|
||||
Keys.VK_1, Keys.VK_LEFT, Keys.VK_CONTROL, Keys.VK_2, Keys.VK_SPACE, Keys.VK_ALT, Keys.VK_Q, null/*STOP*/,
|
||||
];
|
||||
|
||||
const KEYBOARD_ROW_0 = 0;
|
||||
|
||||
class RRIOT_6530 {
|
||||
|
||||
regs = new Uint8Array(16);
|
||||
ina : number = 0;
|
||||
inb : number = 0;
|
||||
|
||||
read(a:number) : number {
|
||||
//console.log('read', hex(a), hex(this.regs[a]));
|
||||
return this.regs[a];
|
||||
}
|
||||
|
||||
write(a:number,v:number) {
|
||||
this.regs[a] = v;
|
||||
//console.log('write', hex(a), hex(v));
|
||||
}
|
||||
|
||||
input_a() { return this.ina & ~this.regs[1]; }
|
||||
input_b() { return this.inb & ~this.regs[1]; }
|
||||
output_a() { return (this.regs[0] ^ 0xff) | this.regs[1]; }
|
||||
output_b() { return (this.regs[2] ^ 0xff) | this.regs[3]; }
|
||||
}
|
||||
|
||||
export class KIM1 extends BasicHeadlessMachine {
|
||||
cpuFrequency = 1000000;
|
||||
defaultROMSize = 0x1000;
|
||||
|
||||
cpu = new MOS6502();
|
||||
ram = new Uint8Array(0x1800);
|
||||
bios : Uint8Array;
|
||||
|
||||
rriot1 : RRIOT_6530 = new RRIOT_6530();
|
||||
rriot2 : RRIOT_6530 = new RRIOT_6530();
|
||||
digits = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(KIM1_BIOS_LZG)));
|
||||
this.connectCPUMemoryBus(this);
|
||||
}
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0x1700, 0x173f, 0x000f, (a) => { return this.readIO_1(a); }],
|
||||
[0x1740, 0x177f, 0x000f, (a) => { return this.readIO_2(a); }],
|
||||
[0x0000, 0x17ff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
[0x1800, 0x1fff, 0x07ff, (a) => { return this.bios[a]; }],
|
||||
], {gmask:0x1fff});
|
||||
|
||||
write = newAddressDecoder([
|
||||
[0x1700, 0x173f, 0x000f, (a,v) => { return this.writeIO_1(a,v); }],
|
||||
[0x1740, 0x177f, 0x000f, (a,v) => { return this.writeIO_2(a,v); }],
|
||||
[0x0000, 0x17ff, 0x1fff, (a,v) => { this.ram[a] = v; }],
|
||||
], {gmask:0x1fff});
|
||||
|
||||
readConst(a:number) : number {
|
||||
return this.read(a);
|
||||
}
|
||||
|
||||
readIO_1(a:number) : number {
|
||||
return this.rriot1.read(a);
|
||||
}
|
||||
|
||||
writeIO_1(a:number, v:number) {
|
||||
this.rriot1.write(a,v);
|
||||
}
|
||||
|
||||
readIO_2(a:number) : number {
|
||||
switch (a & 0xf) {
|
||||
case 0x0:
|
||||
let cols = 0;
|
||||
for (let i=0; i<8; i++)
|
||||
if ((this.rriot2.regs[0] & (1<<i)) == 0)
|
||||
cols |= this.inputs[KEYBOARD_ROW_0 + i];
|
||||
//if (cols) console.log(this.rriot1.regs[0], cols);
|
||||
this.rriot2.ina = cols ^ 0xff;
|
||||
}
|
||||
return this.rriot2.read(a);
|
||||
}
|
||||
|
||||
writeIO_2(a:number, v:number) {
|
||||
this.rriot2.write(a,v);
|
||||
// update LED
|
||||
let digit = this.rriot2.output_a();
|
||||
let segments = this.rriot2.output_b();
|
||||
console.log(digit, segments);
|
||||
}
|
||||
|
||||
loadROM(data) {
|
||||
super.loadROM(data);
|
||||
this.ram.set(this.rom, 0x400);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadBIOS(data) {
|
||||
this.bios = padBytes(data, 0x800);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
setKeyInput(key:number, code:number, flags:number) : void {
|
||||
//console.log(key,code,flags);
|
||||
var keymap = KIM1_KEYMATRIX_NOSHIFT;
|
||||
for (var i=0; i<keymap.length; i++) {
|
||||
if (keymap[i] && keymap[i].c == key) {
|
||||
let row = i >> 3;
|
||||
let col = i & 7;
|
||||
// is column selected?
|
||||
if (flags & KeyFlags.KeyDown) {
|
||||
this.inputs[KEYBOARD_ROW_0 + row] |= (1<<col);
|
||||
} else if (flags & KeyFlags.KeyUp) {
|
||||
this.inputs[KEYBOARD_ROW_0 + row] &= ~(1<<col);
|
||||
}
|
||||
console.log(key, row, col, hex(this.inputs[KEYBOARD_ROW_0 + row]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advanceFrame(trap) : number {
|
||||
var clock = 0;
|
||||
while (clock < this.cpuFrequency/60) {
|
||||
if (trap && trap()) break;
|
||||
clock += this.advanceCPU();
|
||||
}
|
||||
return clock;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/jefftranter/6502/blob/master/asm/KIM-1/ROMs/kim.s
|
||||
const KIM1_BIOS_LZG = `TFpHAAAIAAAABY3ivWkoAQsOJSiprY3sFyAyGaknjUIXqb+NQxeiZKkWIHoZytD4qSoo4a35FyBhGa31FyBeGa32KKPtF833F63uF+34F5AkqS8lXeclnegooqICqQQOBTgAhfqF+0xPHCDsJXAg6hlMMxgPGamNDgVrTI3vF61xGI3wF61yGI3xF6kHDgJ8/43pFyBBGk7pFw3pFyUErekXyRbQ7aIKICQaJQHfytD2JUIq8AYlBtHw8yDzGc35F/ANrfkXyQAlDf/wF9CcJQ0gTBmN7RcOBQHuF0z4GCXEKKSiAiV9L/AUIAAa0CPK0PElDEzsFw4CnCWhzecX0Awo4ugX0ASpAPACqf8OBcWt9ReN7Ret9heN7hepYI3vF6kAjecXjegXYKgYbSUB5xet6BdpACUJmGAgTBmoSigBIG8ZmChiYCkPyQoYMAJpB2kwjukXjOoXoAggnhlKsAYooUyRGSDEKEKI0Ouu6Res6hdgoglILEcXEPupfo1EF6mnjUIXDgkHDiKqytDfaGCiBg4FHsMODB4lhw4HHu7tF9AD7u4XYCAkGiAAGiikYMkwMB7JRxAayUAwAxhpCSooAaQEKi7pF4jQ+a3pF6AAYMhgjusXoggOIovqFw3qF43qF8rQ8a3qFypKrusXYCxCFxD7rUYXoP+MKIEUiND9JQow+zjtDgYLByULSf8pgGAOSFsOBJeaDgymJYclW0x1Gv8oHygfKB4oGWsaKCKF82iF8WiF74X6aIXwhfuE9Ib1uobyIIgeTE8cbPoXbP4Xov+aJYmp/43zF6kBLEAX0Bkw+an8GGkBkAPu8xesQBcQ843yDkIbah4gjB4l2x4gLx6iCiAxHkyvHakAhfiF+SBaHskB8AYgrB9M2x0gGR/Q0yWi8MwlBPD0KILvIGofyRUQu8kU8ETJEPAsyREoYRLwL8kT8DEKKAGF/KIEpP/QCrH6BvwqkfpMwxwKJvom+8rQ6vAIqQHQAqkAhf8OgmZjHyihTMgdpe+F+qXwDoR6Wh7JO9D5JRr3hfYgnR+qIJEfKMGF+yjl+ijhivAPJQORJUMlO8rQ8uglB8X20BcowvfQE4rQuaIMDkOaDgLPTxwlD6IR0O4OBNYoofaF9yAvHqk7IKAepfrN9xel+w6iGRipACA7HiDMHyAeHqX2JQOl9yiBTGQcqRiqJVGRJVGgALH6DgUFDgJy8A4IIeb40ALm+UxIHSV6Lx4lJCCeDgcnng4CQCUqTKwdpvKapftIpfpIpfFIpvWk9KXzQMkg8MrJf/AbyQ3w28kK8BzJLvAmyUfw1clR8ArJTPAJTGocDiIgQh1M5xw4pfrpAYX6sALG+0ysHaAApfiR+kzCHaX7DgSOpQ4FlmCiB73VHyCgHsoQ92CF/A6D00wepfwogw6K1UygHob9oggORAQiMPkg1B4g6x6tQBcpgEb+Bf6F/iUJytDvJQym/aX+KkpgogGG/6IAjkEXoj+OQxeiB45CF9h4YKkghf6G/SUkrUIXKf4OInLUHqIIJYVG/mkAJcnK0O4lCgkBJcam/WCt8xeN9Bet8hc46QGwA870F6z0FxDzDggPSk70F5DjCYCw4KADogGp/45CF+joLUAXiND1oAeMQhcJgEn/YA4iXIX5qX+NQReiCaADufgADgPmSB8lAikPKOGI0OslMakAJRlM/h6E/Ki55x+gAIxAFyUOjUAXoH+I0P3o6KT8YOb60ALm+2CiIaABIAIf0AfgJ9D1qRVgoP8KsAPIEPqKKQ9KqpgQAxhpB8rQ+mAYZfeF96X2aQCF9mAgWh4grB8opKX4DqKkG8lHEBcOqaSgBCom+Cb5iA7iZWCl+IX6pfmF+2AAKAMKDU1JSyATUlJFIBO/htvP5u39h//v9/y53vnx////HBwiHB8c`;
|
||||
|
|
@ -0,0 +1,389 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine } from "../devices";
|
||||
import { BaseZ80VDPBasedMachine } from "./vdp_z80";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { TssChannelAdapter, MasterAudio, AY38910_Audio } from "../audio";
|
||||
import { TMS9918A } from "../video/tms9918a";
|
||||
|
||||
|
||||
var MSX_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x2],
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.A, 0, 0x10],
|
||||
[Keys.B, 0, 0x20],
|
||||
|
||||
[Keys.P2_UP, 1, 0x1],
|
||||
[Keys.P2_DOWN, 1, 0x2],
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_RIGHT, 1, 0x8],
|
||||
[Keys.P2_A, 1, 0x10],
|
||||
[Keys.P2_B, 1, 0x20],
|
||||
|
||||
[Keys.ANYKEY, 2, 0x0],
|
||||
]);
|
||||
|
||||
const JOY_INPUT_0 = 0;
|
||||
const JOY_INPUT_1 = 1;
|
||||
const KEYBOARD_ROW_0 = 16;
|
||||
|
||||
const MSX_KEYMATRIX_INTL_NOSHIFT = [
|
||||
Keys.VK_7, Keys.VK_6, Keys.VK_5, Keys.VK_4, Keys.VK_3, Keys.VK_2, Keys.VK_1, Keys.VK_0,
|
||||
Keys.VK_SEMICOLON, Keys.VK_CLOSE_BRACKET, Keys.VK_OPEN_BRACKET, Keys.VK_BACK_SLASH, Keys.VK_EQUALS, Keys.VK_MINUS, Keys.VK_9, Keys.VK_8,
|
||||
Keys.VK_B, Keys.VK_A, null/*DEAD*/, Keys.VK_SLASH, Keys.VK_PERIOD, Keys.VK_COMMA, Keys.VK_ACUTE, Keys.VK_QUOTE,
|
||||
Keys.VK_J, Keys.VK_I, Keys.VK_H, Keys.VK_G, Keys.VK_F, Keys.VK_E, Keys.VK_D, Keys.VK_C,
|
||||
Keys.VK_R, Keys.VK_Q, Keys.VK_P, Keys.VK_O, Keys.VK_N, Keys.VK_M, Keys.VK_L, Keys.VK_K,
|
||||
Keys.VK_Z, Keys.VK_Y, Keys.VK_X, Keys.VK_W, Keys.VK_V, Keys.VK_U, Keys.VK_T, Keys.VK_S,
|
||||
Keys.VK_F3, Keys.VK_F2, Keys.VK_F1, null, Keys.VK_CAPS_LOCK, null, Keys.VK_CONTROL, Keys.VK_SHIFT,
|
||||
Keys.VK_ENTER, null, Keys.VK_BACK_SPACE, null, Keys.VK_TAB, Keys.VK_ESCAPE, Keys.VK_F5, Keys.VK_F4,
|
||||
Keys.VK_RIGHT, Keys.VK_DOWN, Keys.VK_UP, Keys.VK_LEFT, Keys.VK_DELETE, Keys.VK_INSERT, Keys.VK_HOME, Keys.VK_SPACE,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
// TODO: null keycodes
|
||||
];
|
||||
|
||||
/// standard emulator
|
||||
|
||||
interface MSXSlot {
|
||||
read(addr:number) : number;
|
||||
write(addr:number, val:number) : void;
|
||||
}
|
||||
|
||||
export class MSX1 extends BaseZ80VDPBasedMachine {
|
||||
|
||||
numVisibleScanlines = 240;
|
||||
defaultROMSize = 0x8000;
|
||||
ram = new Uint8Array(0x10000);
|
||||
|
||||
vdp : TMS9918A;
|
||||
bios : Uint8Array;
|
||||
slots : MSXSlot[];
|
||||
slotmask : number = 0;
|
||||
ppi_c : number = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.init(this, this.newIOBus(), new AY38910_Audio(new MasterAudio()));
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(MSX1_BIOS_LZG)));
|
||||
// skip splash screen
|
||||
this.bios[0xdd5] = 0;
|
||||
this.bios[0xdd6] = 0;
|
||||
this.bios[0xdd7] = 0;
|
||||
// slot definitions
|
||||
this.slots = [
|
||||
// slot 0 : BIOS
|
||||
{
|
||||
read: (a) => { return this.bios[a] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 1: cartridge
|
||||
{
|
||||
read: (a) => { return this.rom[a - 0x4000] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 2: cartridge
|
||||
{
|
||||
read: (a) => { return this.rom[a - 0x4000] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 3 : RAM
|
||||
{
|
||||
read: (a) => { return this.ram[a] | 0; },
|
||||
write: (a,v) => { this.ram[a] = v; }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
getKeyboardMap() { return MSX_KEYCODE_MAP; }
|
||||
|
||||
// http://map.grauw.nl/articles/keymatrix.php
|
||||
getKeyboardFunction() {
|
||||
return (o,key,code,flags) => {
|
||||
//console.log(o,key,code,flags);
|
||||
var keymap = MSX_KEYMATRIX_INTL_NOSHIFT;
|
||||
for (var i=0; i<keymap.length; i++) {
|
||||
if (keymap[i] && keymap[i].c == key) {
|
||||
let row = i >> 3;
|
||||
let bit = 7 - (i & 7);
|
||||
//console.log(key, row, bit);
|
||||
if (flags & KeyFlags.KeyDown) {
|
||||
this.inputs[KEYBOARD_ROW_0+row] |= (1<<bit);
|
||||
} else if (flags & KeyFlags.KeyUp) {
|
||||
this.inputs[KEYBOARD_ROW_0+row] &= ~(1<<bit);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
read = (a) => {
|
||||
let shift = (a >> 14) << 1;
|
||||
let slotnum = (this.slotmask >> shift) & 3;
|
||||
let slot = this.slots[slotnum];
|
||||
return slot ? slot.read(a) : 0;
|
||||
};
|
||||
|
||||
write = (a,v) => {
|
||||
let shift = (a >> 14) << 1;
|
||||
let slotnum = (this.slotmask >> shift) & 3;
|
||||
let slot = this.slots[slotnum];
|
||||
if (slot) slot.write(a, v);
|
||||
};
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0x98: return this.vdp.readData();
|
||||
case 0x99: return this.vdp.readStatus();
|
||||
case 0xa2:
|
||||
if (this.psg.currentRegister() == 14) return ~this.inputs[JOY_INPUT_0]; // TODO: joy 1?
|
||||
else return this.psg.readData();
|
||||
case 0xa8: return this.slotmask;
|
||||
case 0xa9: return ~this.inputs[KEYBOARD_ROW_0 + (this.ppi_c & 15)];
|
||||
case 0xaa: return this.ppi_c; // TODO?
|
||||
//default: throw new EmuHalt("Read I/O " + hex(addr));
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr, val) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr) {
|
||||
case 0x98: this.vdp.writeData(val); break;
|
||||
case 0x99: this.vdp.writeAddress(val); break;
|
||||
case 0xa8: this.slotmask = val; break;
|
||||
case 0xaa: this.ppi_c = val; break;
|
||||
case 0xab: // command register, modifies PPI C
|
||||
let ibit = (val >> 1) & 7;
|
||||
this.ppi_c = (this.ppi_c & ~(1<<ibit)) | ((val&1)<<ibit);
|
||||
break;
|
||||
case 0xa0: this.psg.selectRegister(val); break;
|
||||
case 0xa1: this.psg.setData(val); break;
|
||||
case 0xfc:
|
||||
case 0xfd:
|
||||
case 0xfe:
|
||||
case 0xff:
|
||||
break; // memory mapper (MSX2)
|
||||
//default: throw new EmuHalt("Write I/O " + hex(addr));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
vdpInterrupt() {
|
||||
this.probe.logInterrupt(0xff);
|
||||
this.cpu.interrupt(0xff); // RST 0x38
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.slotmask = state['slotmask'];
|
||||
this.ppi_c = state['ppi_c'];
|
||||
this.psg.selectRegister(state['psgRegister']);
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['slotmask'] = this.slotmask;
|
||||
state['ppi_c'] = this.ppi_c;
|
||||
state['psgRegister'] = this.psg.currentRegister();
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.slotmask = 0;
|
||||
this.ppi_c = 0;
|
||||
}
|
||||
/* TODO
|
||||
resume() {
|
||||
super.resume();
|
||||
this.resetInputs();
|
||||
}*/
|
||||
resetInputs() {
|
||||
// clear keyboard matrix
|
||||
this.inputs.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
|
||||
/*
|
||||
C-BIOS is a BIOS compatible with the MSX BIOS
|
||||
C-BIOS was written from scratch by BouKiCHi
|
||||
C-BIOS is available for free, including its source code (2-clause BSD license)
|
||||
C-BIOS can be shipped with MSX emulators so they are usable out-of-the-box without copyright issues
|
||||
|
||||
http://cbios.sourceforge.net/
|
||||
*/
|
||||
var MSX1_BIOS_LZG = `
|
||||
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
|
||||
KERwgHCIO4RKkwL///CTAQ+TAVFEkwQ7yBM8UZpRzMCTBVFkUWz8kwUDkwU/kwURIkSIk2KIRCIR
|
||||
k2L+fDg7Z2IQOHz+gMDg8ODAgAABAwcPBwMBAP9+PBgYPH7/gcPn///nw4E7AlQ7Big7AmQ7BJST
|
||||
BFFUMzPMzJNiABAoKHw7o+g4kyE7AltQiKhQOwy+k+JRbDsG3JMCOwjaaJA7gnBgkOCQkOCAAPiI
|
||||
O4KpAAD4UJMBSFEISCBAiDtDmHiQOyQmiIjIsFFaUFAgk0FwIHCoqJOBOyK+O6WAiFDYO6KgMEg7
|
||||
ZLBQqDsCiAAQUWBAURmA4IA7BSA7gwA7oo87Alg7wyhRATujmJPhO6Owk+I7ZbqTA8BRIFEmOyLI
|
||||
aLCTQjuCiTsEXzB4eJPjUUOTYRwQEJBQMBAA4JCQUYxgEGA7JEhwkwMAqlWTJMXl9Ven8/zeJPHh
|
||||
5dXl9VoDDN5vRz78zfgjX0Xx5gOT4UfbqFejsOHNgPN70fXLesQaJfHhwckEBcgHBxD8yeVX1VG4
|
||||
weHV5TsMNno7CjZZzYXz0eVRtuHJCNnz/eXx3eXhV6dRMtVRIUcO/N3l8VF3hygHywDLAT0g+SFn
|
||||
JOXbqPWhsNnDjPNRK9FRtgjZyfPlb1EqPqvGVQXyeyRXOwNuZ0c+wAcHBfKLJF8vT3qjR32n8tYk
|
||||
Dw/mA+XFOwUipSSjR3rmwGfbqG/mwLRaBAxiobBPMv//fdOoIcX8euYDhW98zgBneXfB4duoobDT
|
||||
qOHJVw8PX+bAb9uoT+Y/tdOoe+YDb3wmAxgCKSnWQDD6fC9nUX5fpLVRPm9506hRPE8GAH0hxfwJ
|
||||
d8l6Dw/mwEdRs7BRM1EbOwcaUVlzydOoXhgD06hzetOoUQQIzZjzCPGT4cnd6QBaBQ87MC4yOTsk
|
||||
8mNiaW9zLnNmLm5ldA0KQ2hhcmFjdGVyIHNldDogVVMNCkluUQdydXB0IGZyZXF1ZW5jeTogNjBI
|
||||
eg0KS2V5Ym9hcmQgdHlwZVHmkyQASW5pdCBST00gaW4gc2xvdDogAENhbm5vdCBleGVjdXRlIGEg
|
||||
QkFTSUNRXS5RKUVSUk9SOgBNRU1PUlkgTk9UIEZPVU5ELgBDQUxMRURRCk4gRVhJU1RJTkdR8i4A
|
||||
U1RBQ0sgUbIuADsCb05vIGNhcnRyaWRnZSBmb3VuZC5RT1RoaXMgdmVyc2lvbiBvZiA7BfxjYW4N
|
||||
Cm9ubHkgc3RRLjsIM3NRLlBsZWFzZSByZVHUeW91ciBNU1gNCihlbXVsYXRvcikgd2l0aCBhOwgw
|
||||
DQppbnNlcnRlZC4AMDEyMzQ1Njc4OS09XFtdOwAnYCwuLwBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2
|
||||
d3h5eikhQCMkJV4mKihfK3x7fToifjw+PwBBQkNERUZHSElKS0xNTk9QUQBSU1RVVldYWVoACayr
|
||||
uu+99PvsBxfxHgENBgW78/IdAMQRvMfNFBUT3MbdyAsbwtvMGNISwBrPHBkPCgD9/AAA9QAACB/w
|
||||
FgIOBAP3rq/2AP4A+sHO1BDW38reyQzTw9fLqdEAxdXQ+ar465/Zv5uY4OHnh+7pAO3at7nlhqan
|
||||
AISXjYuMlIGxoZGzteakoqODkwCJloKViIqghditnr6cnQAA4jvCpOjqtrjkjwCoAI5RBQCZmrAA
|
||||
krK0AKUA41FHO4QvkwgbCQAIAA0gDAAAHR4fHCorLzspMywuAIBwgQCCAYT1h1oEAnWTH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+THpMdkwPF5cEbe7IoAwkY+FoDGKu1Mc2fCT4g
|
||||
0y560y970y/x4clST01CQVM7H6CTH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5MekxyTBj46kz+T
|
||||
P5Ms5fUhSTpaBRvgWgMMYHN0YXRlbWVudHMgYXJlIG5vdCBpbXBsUYtlZCB5ZXQ7P7uTH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TBc2O
|
||||
WgpBt5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5MfWh5ixDsfeZMfkx+TH5Mfkx+TH5Mfkx+TH5MeAOX1ISJ9WgQ60XVua25v
|
||||
d25AN0QxNwA5fd0hiQHNPxrJ3SGFAcM/Wh9abpMeOw71H347DfVFMTQ735aTH5Mfkx6TC1oNaeX/
|
||||
zW8AWgdqSmIAKiL5AQADPgDNVgABAALNRwABA5+TogQAk6FaA3T+B5MBR1oDcC9aA3AqyfNRIyok
|
||||
US4ECesBMAMhvIDNXFHJAQnrKgQACQEAA1FKyfM7Bxvsg1HJEQABGVEWPvFReyL5AYQACeshHIcG
|
||||
CsXl1QEYAFEd4QEgUQzhUQUJwRDpO01I/viTYh9/k6E/OwwSHzsIAvj+OwYDfx9aA1VJ/vz48B8/
|
||||
US2TBVFIAFG+kwk7CS8Afz8f+Pw7CwdaBVbh/v4fP387CUH+/DsGUPiTAfCTAR+TAT+TAVFIUdBR
|
||||
zTsGKzsGCDsHJMffUQ3wOwi3WgZcfjsDen8//pMB/Pz8fDsGKAAA/PgfP3/+UWnwf1Ef/FFk8FFr
|
||||
fz8fH/D++PAff3z8+D8/f5MDOwZMOwYIOwaYUUdRUFEzkwN8PDw8x5MBHx7j45Pi/Pjw8FFhOwb/
|
||||
OwUfkwT+fzstdn87B7hRKz9+kwFRHvAfHz9+OwJEOwK0k+J4eHh8UVg/OyRI/vz8+DsDTzsD0loI
|
||||
XXOTBTsEcfD4/HyTAlEDf39/PzsKcFGIOwJkOwwv/B+TgfA7Itw8jx9/OwJ0n5MBfH5+f+M+Pz87
|
||||
JE7wPlEiOwZEOyI6OyLQ/vjxPPA7Isc7JS1RkHz8+DsiaAA/OyJV8Pj8k8L4UT07TRc7TTU7BwY7
|
||||
BFz++D87S3k7SYo7CEB/OwovOwYCOyJQ/viTofA7Tac7AvaTCztz8gkJk2KQkAmTgZNhkxFRGFGX
|
||||
OwUnkAGTARmTAZGRkTsHHFEEkQmRGRlRVJMGOwYIOwcZOwUoOwU4kTsGeDsHETsFIFEUUbg7QoCT
|
||||
CvGTAVFQCFEfCJCQkJPjk2GBUSyBUS07BhyTgQ8Pk8EPkwFRaJPiH1FBUVZRUTsJSJPiURFRhFEu
|
||||
D/FRRlHBUWtRnzsFI/GAkJCAkICAOwJwCAkICAkYGRkYGRgYGVGVOwdEAQE7B1k7B1w7Bho7BnQ7
|
||||
JBg7CA6TBFHOOwSsOwJzCAg7Al8ICfHx4ZNB4VE0Hh+T4lFIHx5RBJPlUVCT4VF4k+JRkPHhk+E7
|
||||
BWA7BkoPDx47BXA7BEgICAhaA1/Qk+GTA1ETGJMC4ZMBDpMBUWST4x4eUQhRTuHhHpNikwFRhDsG
|
||||
AVEnUUZRJx47BTCT4lHSUZ4BAVE5OwsQUU07BnQYGBiBkwIICDtEClELUQZRDTsHkFGak+GTAxiT
|
||||
pBiBUW47BrRRq1EsUVU7CEA7AliT4pMCOwffUYWAURCEOwUhBDtmAwSTDVHOAICAgYKDhIWGhzsj
|
||||
JpMJiImKi4yNjo+QjJMK45GSkwCUjJWWUUSXOwkQ5JiZmoyMm5ydnp+goaKjnaSlpp2TAYzkp6ip
|
||||
jIyqq4yMrK2ur4yMsLGys7OztIzktbVRLraTYbe4ubqMjLu8vb6+v8CM5MHCw4yMxMXFxsfIycrL
|
||||
zM3OxcXFz9CM5NHC0jsLgZMF5NPU1dbX2Nna2zsHC1YwLjI55IDc3d7C3+Dh4pMM5VoGXW2TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+THpMc/w==
|
||||
`;
|
|
@ -0,0 +1,151 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine } from "../devices";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu";
|
||||
import { TssChannelAdapter, MasterAudio, AY38910_Audio } from "../audio";
|
||||
|
||||
// http://www.computerarcheology.com/Arcade/
|
||||
|
||||
const MW8080BW_PRESETS = [
|
||||
{ id: 'gfxtest.c', name: 'Graphics Test' },
|
||||
{ id: 'shifter.c', name: 'Sprite w/ Bit Shifter' },
|
||||
{ id: 'game2.c', name: 'Cosmic Impalas' },
|
||||
];
|
||||
|
||||
const SPACEINV_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 1, 0x10], // P1
|
||||
[Keys.LEFT, 1, 0x20],
|
||||
[Keys.RIGHT, 1, 0x40],
|
||||
[Keys.P2_A, 2, 0x10], // P2
|
||||
[Keys.P2_LEFT, 2, 0x20],
|
||||
[Keys.P2_RIGHT, 2, 0x40],
|
||||
[Keys.SELECT, 1, 0x1],
|
||||
[Keys.START, 1, 0x4],
|
||||
[Keys.P2_START, 1, 0x2],
|
||||
]);
|
||||
|
||||
const INITIAL_WATCHDOG = 256;
|
||||
const PIXEL_ON = 0xffeeeeee;
|
||||
const PIXEL_OFF = 0xff000000;
|
||||
|
||||
export class Midway8080 extends BasicScanlineMachine {
|
||||
|
||||
cpuFrequency = 1996800; // MHz
|
||||
canvasWidth = 256;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 224;
|
||||
cpuCyclesPerLine = Math.floor(1996800 / (262*60));
|
||||
defaultROMSize = 0x2000;
|
||||
rotate = -90;
|
||||
sampleRate = 1;
|
||||
|
||||
bitshift_offset = 0;
|
||||
bitshift_register = 0;
|
||||
watchdog_counter;
|
||||
|
||||
cpu: Z80 = new Z80();
|
||||
ram = new Uint8Array(0x2000);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.connectCPUMemoryBus(this);
|
||||
this.connectCPUIOBus(this.newIOBus());
|
||||
this.handler = newKeyboardHandler(this.inputs, SPACEINV_KEYCODE_MAP);
|
||||
}
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0x0000, 0x1fff, 0x1fff, (a) => { return this.rom ? this.rom[a] : 0; }],
|
||||
[0x2000, 0x3fff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
]);
|
||||
write = newAddressDecoder([
|
||||
[0x2000, 0x23ff, 0x3ff, (a, v) => { this.ram[a] = v; }],
|
||||
[0x2400, 0x3fff, 0x1fff, (a, v) => {
|
||||
this.ram[a] = v;
|
||||
var ofs = (a - 0x400) << 3;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
this.pixels[ofs + i] = (v & (1 << i)) ? PIXEL_ON : PIXEL_OFF;
|
||||
}
|
||||
//if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter
|
||||
}],
|
||||
]);
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
addr &= 0x3;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return this.inputs[addr];
|
||||
case 3:
|
||||
return (this.bitshift_register >> (8 - this.bitshift_offset)) & 0xff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr, val) => {
|
||||
addr &= 0x7;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr) {
|
||||
case 2:
|
||||
this.bitshift_offset = val & 0x7;
|
||||
break;
|
||||
case 3:
|
||||
case 5:
|
||||
// TODO: sound
|
||||
break;
|
||||
case 4:
|
||||
this.bitshift_register = (this.bitshift_register >> 8) | (val << 8);
|
||||
break;
|
||||
case 6:
|
||||
this.watchdog_counter = INITIAL_WATCHDOG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
startScanline() {
|
||||
}
|
||||
|
||||
drawScanline() {
|
||||
// at end of scanline
|
||||
if (this.scanline == 95)
|
||||
this.interrupt(0xcf); // RST $8
|
||||
else if (this.scanline == 223)
|
||||
this.interrupt(0xd7); // RST $10
|
||||
}
|
||||
|
||||
interrupt(data:number) {
|
||||
this.probe.logInterrupt(data);
|
||||
this.cpu.interrupt(data);
|
||||
}
|
||||
|
||||
advanceFrame(trap) : number {
|
||||
if (this.watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED"); // TODO: alert on video
|
||||
this.reset();
|
||||
}
|
||||
return super.advanceFrame(trap);
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.bitshift_register = state.bsr;
|
||||
this.bitshift_offset = state.bso;
|
||||
this.watchdog_counter = state.wdc;
|
||||
}
|
||||
saveState() {
|
||||
var state: any = super.saveState();
|
||||
state.bsr = this.bitshift_register;
|
||||
state.bso = this.bitshift_offset;
|
||||
state.wdc = this.watchdog_counter;
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine } from "../devices";
|
||||
import { BaseZ80VDPBasedMachine } from "./vdp_z80";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { TssChannelAdapter, MasterAudio, SN76489_Audio } from "../audio";
|
||||
import { TMS9918A, SMSVDP } from "../video/tms9918a";
|
||||
|
||||
// http://www.smspower.org/Development/Index
|
||||
// http://www.smspower.org/uploads/Development/sg1000.txt
|
||||
// http://www.smspower.org/uploads/Development/richard.txt
|
||||
// http://www.smspower.org/uploads/Development/msvdp-20021112.txt
|
||||
// http://www.smspower.org/uploads/Development/SN76489-20030421.txt
|
||||
|
||||
var SG1000_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x2],
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.A, 0, 0x10],
|
||||
[Keys.B, 0, 0x20],
|
||||
|
||||
[Keys.P2_UP, 0, 0x40],
|
||||
[Keys.P2_DOWN, 0, 0x80],
|
||||
[Keys.P2_LEFT, 1, 0x1],
|
||||
[Keys.P2_RIGHT, 1, 0x2],
|
||||
[Keys.P2_A, 1, 0x4],
|
||||
[Keys.P2_B, 1, 0x8],
|
||||
[Keys.VK_BACK_SLASH, 1, 0x10], // reset
|
||||
]);
|
||||
|
||||
export class SG1000 extends BaseZ80VDPBasedMachine {
|
||||
|
||||
numVisibleScanlines = 240;
|
||||
defaultROMSize = 0xc000;
|
||||
ram = new Uint8Array(0x400);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.init(this, this.newIOBus(), new SN76489_Audio(new MasterAudio()));
|
||||
}
|
||||
|
||||
getKeyboardMap() { return SG1000_KEYCODE_MAP; }
|
||||
|
||||
vdpInterrupt() {
|
||||
this.probe.logInterrupt(0xff);
|
||||
return this.cpu.interrupt(0xff); // RST 0x38
|
||||
}
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x3ff, (a) => { return this.ram[a]; }],
|
||||
[0x0000, 0xbfff, 0xffff, (a) => { return this.rom[a]; }],
|
||||
]);
|
||||
write = newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x3ff, (a,v) => { this.ram[a] = v; }],
|
||||
]);
|
||||
|
||||
getVCounter() : number { return 0; }
|
||||
getHCounter() : number { return 0; }
|
||||
setMemoryControl(v:number) { }
|
||||
setIOPortControl(v:number) { }
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr:number) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x40: return this.getVCounter();
|
||||
case 0x41: return this.getHCounter();
|
||||
case 0x80: return this.vdp.readData();
|
||||
case 0x81: return this.vdp.readStatus();
|
||||
case 0xc0: return this.inputs[0] ^ 0xff;
|
||||
case 0xc1: return this.inputs[1] ^ 0xff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x00: return this.setMemoryControl(val);
|
||||
case 0x01: return this.setIOPortControl(val);
|
||||
case 0x40:
|
||||
case 0x41: return this.psg.setData(val);
|
||||
case 0x80: return this.vdp.writeData(val);
|
||||
case 0x81: return this.vdp.writeAddress(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class SMS extends SG1000 {
|
||||
|
||||
cartram = new Uint8Array(0);
|
||||
pagingRegisters = new Uint8Array(4);
|
||||
romPageMask : number;
|
||||
latchedHCounter = 0;
|
||||
ioControlFlags = 0;
|
||||
// TODO: hide bottom scanlines
|
||||
ram = new Uint8Array(0x2000);
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new SMSVDP(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.pagingRegisters.set([0,0,1,2]);
|
||||
}
|
||||
|
||||
getVCounter() {
|
||||
var y = this.scanline;
|
||||
return (y <= 0xda) ? (y) : (y - 6);
|
||||
}
|
||||
getHCounter() {
|
||||
return this.latchedHCounter;
|
||||
}
|
||||
computeHCounter() {
|
||||
return 0;
|
||||
/*
|
||||
var t0 = this.startLineTstates;
|
||||
var t1 = this.cpu.getTstates();
|
||||
return (t1-t0) & 0xff; // TODO
|
||||
*/
|
||||
}
|
||||
setIOPortControl(v:number) {
|
||||
if ((v ^ this.ioControlFlags) & 0xa0) { // either joystick TH pin
|
||||
this.latchedHCounter = this.computeHCounter();
|
||||
//console.log("H:"+hex(this.latchedHCounter)+" V:"+hex(this.getVCounter()));
|
||||
}
|
||||
this.ioControlFlags = v;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
[0x0000, 0x03ff, 0x3ff, (a) => { return this.rom[a]; }],
|
||||
[0x0400, 0x3fff, 0x3fff, (a) => { return this.getPagedROM(a,1); }],
|
||||
[0x4000, 0x7fff, 0x3fff, (a) => { return this.getPagedROM(a,2); }],
|
||||
[0x8000, 0xbfff, 0x3fff, (a) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
return this.cartram[(reg0 & 0x4) ? a+0x4000 : a];
|
||||
} else {
|
||||
return this.getPagedROM(a,3);
|
||||
}
|
||||
}],
|
||||
]);
|
||||
|
||||
write = newAddressDecoder([
|
||||
[0xc000, 0xfffb, 0x1fff, (a,v) => {
|
||||
this.ram[a] = v;
|
||||
}],
|
||||
[0xfffc, 0xffff, 0x3, (a,v) => {
|
||||
this.pagingRegisters[a] = v;
|
||||
this.ram[a+0x1ffc] = v;
|
||||
}],
|
||||
[0x8000, 0xbfff, 0x3fff, (a,v) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
if (this.cartram.length == 0)
|
||||
this.cartram = new Uint8Array(0x8000); // create cartridge RAM lazily
|
||||
this.cartram[(reg0 & 0x4) ? a+0x4000 : a] = v;
|
||||
}
|
||||
}],
|
||||
]);
|
||||
|
||||
loadROM(data:Uint8Array) {
|
||||
if (data.length <= 0xc000) {
|
||||
this.rom = padBytes(data, 0xc000);
|
||||
this.romPageMask = 3; // only pages 0, 1, 2
|
||||
} else {
|
||||
switch (data.length) {
|
||||
case 0x10000:
|
||||
case 0x20000:
|
||||
case 0x40000:
|
||||
case 0x80000:
|
||||
this.rom = data;
|
||||
this.romPageMask = (data.length >> 14) - 1; // div $4000
|
||||
break;
|
||||
default:
|
||||
throw "Unknown rom size: $" + hex(data.length);
|
||||
}
|
||||
}
|
||||
//console.log("romPageMask: " + hex(this.romPageMask));
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.pagingRegisters.set(state.pr);
|
||||
this.cartram.set(state.cr);
|
||||
this.latchedHCounter = state.lhc;
|
||||
this.ioControlFlags = state.iocf;
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['pr'] = this.pagingRegisters.slice(0);
|
||||
state['cr'] = this.cartram.slice(0);
|
||||
state['lhc'] = this.latchedHCounter;
|
||||
state['iocf'] = this.ioControlFlags;
|
||||
return state;
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'SMS': // TODO
|
||||
return super.getDebugInfo(category, state) +
|
||||
"\nBank Regs: " + this.pagingRegisters + "\n";
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine, Bus, ProbeAll } from "../devices";
|
||||
import { newAddressDecoder, newKeyboardHandler } from "../emu";
|
||||
import { TssChannelAdapter } from "../audio";
|
||||
import { TMS9918A } from "../video/tms9918a";
|
||||
|
||||
const audioOversample = 2;
|
||||
|
||||
export abstract class BaseZ80VDPBasedMachine extends BasicScanlineMachine {
|
||||
|
||||
cpuFrequency = 3579545; // MHz
|
||||
canvasWidth = 304;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 240;
|
||||
cpuCyclesPerLine = this.cpuFrequency / (262*60);
|
||||
sampleRate = 262*60*audioOversample;
|
||||
overscan = true;
|
||||
|
||||
cpu: Z80 = new Z80();
|
||||
vdp: TMS9918A;
|
||||
psg;
|
||||
audioadapter;
|
||||
|
||||
abstract vdpInterrupt();
|
||||
abstract getKeyboardMap();
|
||||
getKeyboardFunction() { return null; }
|
||||
|
||||
init(membus:Bus, iobus:Bus, psg) {
|
||||
this.connectCPUMemoryBus(membus);
|
||||
this.connectCPUIOBus(iobus);
|
||||
this.handler = newKeyboardHandler(this.inputs, this.getKeyboardMap(), this.getKeyboardFunction());
|
||||
this.psg = psg;
|
||||
this.audioadapter = psg && new TssChannelAdapter(psg.psg, audioOversample, this.sampleRate);
|
||||
}
|
||||
|
||||
connectVideo(pixels) {
|
||||
super.connectVideo(pixels);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
this.vdpInterrupt();
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
}
|
||||
};
|
||||
this.vdp = this.newVDP(this.pixels, cru, true);
|
||||
}
|
||||
|
||||
connectProbe(probe: ProbeAll) : void {
|
||||
super.connectProbe(probe);
|
||||
this.vdp.probe = probe || this.nullProbe;
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new TMS9918A(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
startScanline() {
|
||||
this.audio && this.audioadapter && this.audioadapter.generate(this.audio);
|
||||
}
|
||||
|
||||
drawScanline() {
|
||||
this.vdp.drawScanline(this.scanline);
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.vdp.restoreState(state['vdp']);
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['vdp'] = this.vdp.getState();
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.vdp.reset();
|
||||
this.psg.reset();
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return ['CPU','Stack','VDP'];
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
}
|
||||
}
|
||||
vdpStateToLongString(ppu) {
|
||||
return this.vdp.getRegsString();
|
||||
}
|
||||
readVRAMAddress(a : number) : number {
|
||||
return this.vdp.ram[a & 0x3fff];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicScanlineMachine } from "../devices";
|
||||
import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../emu";
|
||||
import { TssChannelAdapter, MasterAudio, AY38910_Audio } from "../audio";
|
||||
|
||||
const CARNIVAL_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 2, -0x20],
|
||||
[Keys.B, 2, -0x40],
|
||||
[Keys.LEFT, 1, -0x10],
|
||||
[Keys.RIGHT, 1, -0x20],
|
||||
[Keys.UP, 1, -0x40],
|
||||
[Keys.DOWN, 1, -0x80],
|
||||
[Keys.START, 2, -0x10],
|
||||
[Keys.P2_START, 3, -0x20],
|
||||
[Keys.SELECT, 3, 0x8],
|
||||
]);
|
||||
|
||||
const XTAL = 15468000.0;
|
||||
const scanlinesPerFrame = 0x106;
|
||||
const vblankStart = 0xe0;
|
||||
const vsyncStart = 0xec;
|
||||
const vsyncEnd = 0xf0;
|
||||
const cpuFrequency = XTAL / 8;
|
||||
const hsyncFrequency = XTAL / 3 / scanlinesPerFrame;
|
||||
const vsyncFrequency = hsyncFrequency / 0x148;
|
||||
const cpuCyclesPerLine = cpuFrequency / hsyncFrequency;
|
||||
const timerFrequency = 500; // input 2 bit 0x8
|
||||
const cyclesPerTimerTick = cpuFrequency / (2 * timerFrequency);
|
||||
const audioOversample = 2;
|
||||
const audioSampleRate = 60 * scanlinesPerFrame; // why not hsync?
|
||||
|
||||
export class VicDual extends BasicScanlineMachine {
|
||||
|
||||
cpuFrequency = XTAL / 8; // MHz
|
||||
canvasWidth = 256;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 224;
|
||||
defaultROMSize = 0x4040;
|
||||
sampleRate = audioSampleRate * audioOversample;
|
||||
cpuCyclesPerLine = cpuCyclesPerLine|0;
|
||||
rotate = -90;
|
||||
|
||||
cpu: Z80 = new Z80();
|
||||
ram = new Uint8Array(0x1000);
|
||||
psg: AY38910_Audio;
|
||||
display: VicDualDisplay;
|
||||
audioadapter;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.connectCPUMemoryBus(this);
|
||||
this.connectCPUIOBus(this.newIOBus());
|
||||
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
|
||||
this.display = new VicDualDisplay();
|
||||
this.handler = newKeyboardHandler(this.inputs, CARNIVAL_KEYCODE_MAP, this.getKeyboardFunction());
|
||||
this.psg = new AY38910_Audio(new MasterAudio());
|
||||
this.audioadapter = new TssChannelAdapter(this.psg.psg, audioOversample, this.sampleRate);
|
||||
}
|
||||
|
||||
getKeyboardFunction() {
|
||||
return (o) => {
|
||||
// reset when coin inserted
|
||||
if (o.index == 3 && o.mask == 0x8) {
|
||||
this.cpu.reset();
|
||||
console.log("coin inserted");
|
||||
console.log(this.inputs)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
read = newAddressDecoder([
|
||||
[0x0000, 0x7fff, 0x3fff, (a) => { return this.rom ? this.rom[a] : null; }],
|
||||
[0x8000, 0xffff, 0x0fff, (a) => { return this.ram[a]; }],
|
||||
]);
|
||||
|
||||
write = newAddressDecoder([
|
||||
[0x8000, 0xffff, 0x0fff, (a, v) => { this.ram[a] = v; }],
|
||||
]);
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
return this.inputs[addr & 3];
|
||||
},
|
||||
write: (addr, val) => {
|
||||
if (addr & 0x1) { this.psg.selectRegister(val & 0xf); }; // audio 1
|
||||
if (addr & 0x2) { this.psg.setData(val); }; // audio 2
|
||||
if (addr & 0x8) { }; // TODO: assert coin status
|
||||
if (addr & 0x40) { this.display.palbank = val & 3; }; // palette
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.psg.reset();
|
||||
}
|
||||
|
||||
startScanline() {
|
||||
this.inputs[2] &= ~0x8;
|
||||
this.inputs[2] |= ((this.frameCycles / cyclesPerTimerTick) & 1) << 3;
|
||||
if (this.scanline == vblankStart) this.inputs[1] |= 0x8;
|
||||
if (this.scanline == vsyncEnd) this.inputs[1] &= ~0x8;
|
||||
this.audio && this.audioadapter.generate(this.audio);
|
||||
}
|
||||
|
||||
drawScanline() {
|
||||
this.display.drawScanline(this.ram, this.pixels, this.scanline);
|
||||
}
|
||||
|
||||
loadROM(data) {
|
||||
super.loadROM(data);
|
||||
if (data.length >= 0x4020 && (data[0x4000] || data[0x401f])) {
|
||||
this.display.colorprom = data.slice(0x4000, 0x4020);
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.display.palbank = state.pb;
|
||||
}
|
||||
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['pb'] = this.display.palbank;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
class VicDualDisplay {
|
||||
palbank: number = 0;
|
||||
|
||||
palette = [
|
||||
0xff000000, // black
|
||||
0xff0000ff, // red
|
||||
0xff00ff00, // green
|
||||
0xff00ffff, // yellow
|
||||
0xffff0000, // blue
|
||||
0xffff00ff, // magenta
|
||||
0xffffff00, // cyan
|
||||
0xffffffff // white
|
||||
];
|
||||
|
||||
// default PROM
|
||||
colorprom = [
|
||||
0xe0, 0x60, 0x20, 0x60, 0xc0, 0x60, 0x40, 0xc0,
|
||||
0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0, 0x0e,
|
||||
0xe0, 0xe0, 0xe0, 0xe0, 0x60, 0x60, 0x60, 0x60,
|
||||
0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
|
||||
];
|
||||
|
||||
// videoram 0xc000-0xc3ff
|
||||
// RAM 0xc400-0xc7ff
|
||||
// charram 0xc800-0xcfff
|
||||
drawScanline(ram, pixels: Uint32Array, sl: number) {
|
||||
if (sl >= 224) return;
|
||||
var pixofs = sl * 256;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
var vramofs = (sl >> 3) << 5; // offset in VRAM
|
||||
var yy = sl & 7; // y offset within tile
|
||||
for (var xx = 0; xx < 32; xx++) {
|
||||
var code = ram[vramofs + xx];
|
||||
var data = ram[0x800 + (code << 3) + yy];
|
||||
var col = (code >> 5) + (this.palbank << 3);
|
||||
var color1 = this.palette[(this.colorprom[col] >> 1) & 7];
|
||||
var color2 = this.palette[(this.colorprom[col] >> 5) & 7];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
pixels[outi] = (data & bm) ? color2 : color1;
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,659 +1,29 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, getMousePos, EmuHalt } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray, lpad, rpad, rgb2bgr } from "../util";
|
||||
import { MasterAudio, POKEYDeviceChannel, newPOKEYAudio } from "../audio";
|
||||
|
||||
declare var jt; // for 6502
|
||||
|
||||
// https://atarihq.com/danb/a7800.shtml
|
||||
// https://atarihq.com/danb/files/maria_r1.txt
|
||||
// https://sites.google.com/site/atari7800wiki/
|
||||
import { Atari7800 } from "../machine/atari7800";
|
||||
import { Platform, Base6502MachinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
var Atari7800_PRESETS = [
|
||||
{id:'sprites.dasm', name:'Sprites (ASM)'},
|
||||
{id:'wsync.c', name:'WSYNC'},
|
||||
];
|
||||
|
||||
const SWCHA = 0;
|
||||
const SWCHB = 2;
|
||||
const INPT0 = 8;
|
||||
class Atari7800Platform extends Base6502MachinePlatform<Atari7800> implements Platform {
|
||||
|
||||
const Atari7800_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, INPT0+0, 0x80],
|
||||
[Keys.B, INPT0+1, 0x80],
|
||||
[Keys.SELECT, SWCHB, -0x02],
|
||||
[Keys.START, SWCHB, -0x01],
|
||||
[Keys.UP, SWCHA, -0x10],
|
||||
[Keys.DOWN, SWCHA, -0x20],
|
||||
[Keys.LEFT, SWCHA, -0x40],
|
||||
[Keys.RIGHT, SWCHA, -0x80],
|
||||
|
||||
[Keys.P2_A, INPT0+2, 0x80],
|
||||
[Keys.P2_B, INPT0+3, 0x80],
|
||||
//[Keys.P2_SELECT, 1, 2],
|
||||
//[Keys.P2_START, 1, 3],
|
||||
[Keys.P2_UP, SWCHA, -0x01],
|
||||
[Keys.P2_DOWN, SWCHA, -0x02],
|
||||
[Keys.P2_LEFT, SWCHA, -0x04],
|
||||
[Keys.P2_RIGHT, SWCHA, -0x08],
|
||||
]);
|
||||
|
||||
// http://www.ataripreservation.org/websites/freddy.offenga/megazine/ISSUE5-PALNTSC.html
|
||||
// http://7800.8bitdev.org/index.php/7800_Software_Guide#APPENDIX_4:_FRAME_TIMING
|
||||
const CLK = 3579545;
|
||||
const cpuFrequency = 1789772;
|
||||
const linesPerFrame = 262;
|
||||
const numVisibleLines = 258-16;
|
||||
const colorClocksPerLine = 454; // 456?
|
||||
const colorClocksPreDMA = 28;
|
||||
const romLength = 0xc000;
|
||||
|
||||
// TIA chip
|
||||
|
||||
class TIA {
|
||||
regs = new Uint8Array(0x20);
|
||||
|
||||
reset() {
|
||||
this.regs.fill(0);
|
||||
}
|
||||
read(a : number) : number {
|
||||
return this.regs[a] | 0;
|
||||
}
|
||||
write(a : number, v : number) {
|
||||
this.regs[a] = v;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
regs: this.regs.slice(0)
|
||||
};
|
||||
}
|
||||
loadState(s) {
|
||||
for (let i=0; i<32; i++)
|
||||
this.write(i, s.regs[i]);
|
||||
}
|
||||
static stateToLongString(state) : string {
|
||||
let s = "";
|
||||
s += dumpRAM(state.regs, 0, 32);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// MARIA chip
|
||||
|
||||
class MARIA {
|
||||
bus;
|
||||
profiler;
|
||||
cycles : number = 0;
|
||||
regs = new Uint8Array(0x20);
|
||||
offset : number = -1;
|
||||
dll : number = 0;
|
||||
dlstart : number = 0;
|
||||
dli : boolean = false;
|
||||
h16 : boolean = false;
|
||||
h8 : boolean = false;
|
||||
pixels = new Uint8Array(320);
|
||||
WSYNC : number = 0;
|
||||
|
||||
reset() {
|
||||
this.regs.fill(0);
|
||||
}
|
||||
read(a : number) : number {
|
||||
return this.regs[a] | 0;
|
||||
}
|
||||
write(a : number, v : number) {
|
||||
this.regs[a] = v;
|
||||
if (a == 0x04) this.WSYNC++;
|
||||
//console.log(hex(a), '=', hex(v));
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
regs: this.regs.slice(0),
|
||||
offset: this.offset,
|
||||
dll: this.dll,
|
||||
dlstart: this.dlstart,
|
||||
dli: this.dli,
|
||||
h16: this.h16,
|
||||
h8: this.h8,
|
||||
};
|
||||
}
|
||||
loadState(s) {
|
||||
for (let i=0; i<32; i++)
|
||||
this.write(i, s.regs[i]);
|
||||
this.offset = s.offset;
|
||||
this.dll = s.dll;
|
||||
this.dlstart = s.dlstart;
|
||||
this.dli = s.dli;
|
||||
this.h16 = s.h16;
|
||||
this.h8 = s.h8;
|
||||
}
|
||||
isDMAEnabled() {
|
||||
return (this.regs[0x1c] & 0x60) == 0x40;
|
||||
}
|
||||
getDLLStart() {
|
||||
return (this.regs[0x0c] << 8) + this.regs[0x10];
|
||||
}
|
||||
getCharBaseAddress() {
|
||||
return (this.regs[0x14] << 8) + this.offset;
|
||||
}
|
||||
setVBLANK(b : boolean) {
|
||||
if (b) {
|
||||
this.regs[0x08] |= 0x80;
|
||||
this.offset = -1;
|
||||
this.dll = this.getDLLStart();
|
||||
this.dli = this.bus && (this.bus.read(this.dll) & 0x80) != 0; // if DLI on first zone
|
||||
} else {
|
||||
this.regs[0x08] &= ~0x80;
|
||||
}
|
||||
}
|
||||
readDLLEntry(bus) {
|
||||
this.profiler && this.profiler.logRead(this.dll);
|
||||
let x = bus.read(this.dll);
|
||||
this.offset = (x & 0xf);
|
||||
this.h16 = (x & 0x40) != 0;
|
||||
this.h8 = (x & 0x20) != 0;
|
||||
this.dlstart = (bus.read(this.dll+1)<<8) + bus.read(this.dll+2);
|
||||
//console.log(hex(this.dll,4), this.offset, hex(this.dlstart,4));
|
||||
this.dll = (this.dll + 3) & 0xffff; // TODO: can also only cross 1 page?
|
||||
this.dli = (bus.read(this.dll) & 0x80) != 0; // DLI flag is from next DLL entry
|
||||
}
|
||||
isHoley(a : number) : boolean {
|
||||
if (a & 0x8000) {
|
||||
if (this.h16 && (a & 0x1000)) return true;
|
||||
if (this.h8 && (a & 0x800)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
readDMA(a : number) : number {
|
||||
if (this.isHoley(a))
|
||||
return 0;
|
||||
else {
|
||||
this.cycles += 3;
|
||||
//this.profiler && this.profiler.logRead(a);
|
||||
return this.bus.read(a);
|
||||
}
|
||||
}
|
||||
doDMA(platform : Atari7800Platform) {
|
||||
let bus = this.bus = platform.bus;
|
||||
let profiler = this.profiler = platform.profiler;
|
||||
this.cycles = 0;
|
||||
this.pixels.fill(this.regs[0x0]);
|
||||
if (this.isDMAEnabled()) {
|
||||
this.cycles += 16;
|
||||
// time for a new DLL entry?
|
||||
if (this.offset < 0) {
|
||||
this.readDLLEntry(bus);
|
||||
}
|
||||
// read the DL (only can span two pages)
|
||||
let dlhi = this.dlstart & 0xff00;
|
||||
let dlofs = this.dlstart & 0xff;
|
||||
do {
|
||||
// read DL entry
|
||||
profiler && profiler.logRead(dlhi + ((dlofs+0) & 0x1ff));
|
||||
let b0 = bus.read(dlhi + ((dlofs+0) & 0x1ff));
|
||||
let b1 = bus.read(dlhi + ((dlofs+1) & 0x1ff));
|
||||
if (b1 == 0) break; // end of DL
|
||||
let b2 = bus.read(dlhi + ((dlofs+2) & 0x1ff));
|
||||
let b3 = bus.read(dlhi + ((dlofs+3) & 0x1ff));
|
||||
let indirect = false;
|
||||
// extended header?
|
||||
if ((b1 & 31) == 0) {
|
||||
var pal = b3 >> 5;
|
||||
var width = 32 - (b3 & 31);
|
||||
var xpos = bus.read(dlhi + ((dlofs+4) & 0x1ff));
|
||||
var writemode = b1 & 0x80;
|
||||
indirect = (b1 & 0x20) != 0;
|
||||
dlofs += 5;
|
||||
this.cycles += 10;
|
||||
} else {
|
||||
// direct mode
|
||||
var xpos = b3;
|
||||
var pal = b1 >> 5;
|
||||
var width = 32 - (b1 & 31);
|
||||
var writemode = 0;
|
||||
dlofs += 4;
|
||||
this.cycles += 8;
|
||||
}
|
||||
let gfxadr = b0 + (((b2 + (indirect?0:this.offset)) & 0xff) << 8);
|
||||
xpos *= 2;
|
||||
// copy graphics data (direct)
|
||||
let readmode = (this.regs[0x1c] & 0x3) + (writemode?4:0);
|
||||
// double bytes?
|
||||
let dbl = indirect && (this.regs[0x1c] & 0x10) != 0;
|
||||
if (dbl) { width *= 2; }
|
||||
//if (this.offset == 0) console.log(hex(dla,4), hex(gfxadr,4), xpos, width, pal, readmode);
|
||||
for (var i=0; i<width; i++) {
|
||||
let data = this.readDMA( dbl ? (gfxadr+(i>>1)) : (gfxadr+i) );
|
||||
if (indirect) {
|
||||
let indadr = ((this.regs[0x14] + this.offset) << 8) + data;
|
||||
if (dbl && (i&1)) indadr++;
|
||||
data = this.readDMA(indadr);
|
||||
}
|
||||
// TODO: more modes
|
||||
switch (readmode) {
|
||||
case 0: // 160 A/B
|
||||
for (let j=0; j<4; j++) {
|
||||
var col = (data >> 6) & 3;
|
||||
if (col > 0) {
|
||||
this.pixels[xpos] = this.pixels[xpos+1] = this.regs[(pal<<2) + col];
|
||||
}
|
||||
data <<= 2;
|
||||
xpos = (xpos + 2) & 0x1ff;
|
||||
}
|
||||
break;
|
||||
case 2: // 320 B/D (TODO?)
|
||||
case 3: // 320 A/C
|
||||
for (let j=0; j<8; j++) {
|
||||
var col = (data & 128) ? 1 : 0;
|
||||
if (col > 0) {
|
||||
this.pixels[xpos] = this.regs[(pal<<2) + col];
|
||||
}
|
||||
data <<= 1;
|
||||
xpos = (xpos + 1) & 0x1ff;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (this.cycles < colorClocksPerLine); // TODO?
|
||||
// decrement offset
|
||||
this.offset -= 1;
|
||||
}
|
||||
return this.cycles;
|
||||
}
|
||||
doInterrupt() : boolean {
|
||||
if (this.dli && this.offset < 0) {
|
||||
this.dli = false;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
//return this.dli;// && this.offset == 1;
|
||||
}
|
||||
static stateToLongString(state) : string {
|
||||
let s = "";
|
||||
s += dumpRAM(state.regs, 0, 32);
|
||||
s += "\n DLL: $" + hex((state.regs[0x0c] << 8) + state.regs[0x10],4) + " @ $" + hex(state.dll,4);
|
||||
s += "\n DL: $" + hex(state.dlstart,4);
|
||||
s += "\nOffset: " + state.offset;
|
||||
s += "\n DLI? " + state.dli;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Atari 7800
|
||||
|
||||
class Atari7800Platform extends Base6502Platform implements Platform {
|
||||
|
||||
mainElement : HTMLElement;
|
||||
cpu;
|
||||
ram : Uint8Array;
|
||||
rom : Uint8Array;
|
||||
bios : Uint8Array;
|
||||
bus;
|
||||
video;
|
||||
audio;
|
||||
timer : AnimationTimer;
|
||||
inputs = new Uint8Array(16);
|
||||
regs6532 = new Uint8Array(4);
|
||||
scanline : number = 0;
|
||||
tia : TIA = new TIA();
|
||||
maria : MARIA = new MARIA();
|
||||
|
||||
constructor(mainElement : HTMLElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
}
|
||||
|
||||
getPresets() {
|
||||
return Atari7800_PRESETS;
|
||||
}
|
||||
|
||||
readInput(a:number) : number {
|
||||
this.profiler && this.profiler.logRead(a+0x20);
|
||||
switch (a) {
|
||||
case 0xc: return ~this.inputs[0x8] & 0x80; //INPT4
|
||||
case 0xd: return ~this.inputs[0x9] & 0x80; //INPT5
|
||||
default: return this.inputs[a]|0;
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.cpu = new jt.M6502();
|
||||
this.ram = new Uint8Array(0x1000);
|
||||
//this.bios = new Uint8Array(0x1000); // TODO
|
||||
// TODO: TIA access wastes a cycle
|
||||
this.bus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0008, 0x000d, 0x0f, (a) => { return this.readInput(a); }],
|
||||
[0x0000, 0x001f, 0x1f, (a) => { return this.tia.read(a); }],
|
||||
[0x0020, 0x003f, 0x1f, (a) => { return this.maria.read(a); this.profiler && this.profiler.logRead(a+0x20); }],
|
||||
[0x0040, 0x00ff, 0xff, (a) => { return this.ram[a + 0x800]; }],
|
||||
[0x0100, 0x013f, 0xff, (a) => { return this.bus.read(a); }], // shadow
|
||||
[0x0140, 0x01ff, 0x1ff, (a) => { return this.ram[a + 0x800]; }],
|
||||
[0x0280, 0x02ff, 0x3, (a) => { return this.inputs[a]; }],
|
||||
[0x1800, 0x27ff, 0xffff, (a) => { return this.ram[a - 0x1800]; }],
|
||||
[0x2800, 0x3fff, 0x7ff, (a) => { return this.bus.read(a | 0x2000); }], // shadow
|
||||
[0x4000, 0xffff, 0xffff, (a) => { return this.rom ? this.rom[a - 0x4000] : 0; }],
|
||||
[0x0000, 0xffff, 0xffff, (a) => { return 0; }], // TODO
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x0015, 0x001A, 0x1f, (a,v) => { this.audio.pokey1.setTIARegister(a, v); }],
|
||||
[0x0000, 0x001f, 0x1f, (a,v) => { this.tia.write(a,v); this.profiler && this.profiler.logWrite(a); }],
|
||||
[0x0020, 0x003f, 0x1f, (a,v) => { this.maria.write(a,v); this.profiler && this.profiler.logWrite(a+0x20); }],
|
||||
[0x0040, 0x00ff, 0xff, (a,v) => { this.ram[a + 0x800] = v; }],
|
||||
[0x0100, 0x013f, 0xff, (a,v) => { this.bus.write(a); }], // shadow
|
||||
[0x0140, 0x01ff, 0x1ff, (a,v) => { this.ram[a + 0x800] = v; }],
|
||||
[0x0280, 0x02ff, 0x3, (a,v) => { this.regs6532[a] = v; /*TODO*/ }],
|
||||
[0x1800, 0x27ff, 0xffff, (a,v) => { this.ram[a - 0x1800] = v; }],
|
||||
[0x2800, 0x3fff, 0x7ff, (a,v) => { this.bus.write(a | 0x2000, v); }], // shadow
|
||||
[0xbfff, 0xbfff, 0xffff, (a,v) => { }], // TODO: bank switching?
|
||||
[0x0000, 0xffff, 0xffff, (a,v) => { throw new EmuHalt("Write @ " + hex(a,4) + " " + hex(v,2)); }],
|
||||
]),
|
||||
};
|
||||
this.cpu.connectBus(this.bus);
|
||||
// create video/audio
|
||||
this.video = new RasterVideo(this.mainElement, 320, numVisibleLines);
|
||||
this.audio = newPOKEYAudio(1);
|
||||
this.video.create();
|
||||
setKeyboardFromMap(this.video, this.inputs, Atari7800_KEYCODE_MAP, (o,key,code,flags) => {
|
||||
// TODO
|
||||
});
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
// setup mouse events
|
||||
var rasterPosBreakFn = (e) => {
|
||||
if (e.shiftKey) {
|
||||
var clickpos = getMousePos(e.target, e);
|
||||
this.runEval( (c) => {
|
||||
return (this.getRasterScanline() == (clickpos.y|0));
|
||||
});
|
||||
}
|
||||
};
|
||||
var jacanvas = $("#emulator").find("canvas");
|
||||
jacanvas.mousedown(rasterPosBreakFn);
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
var idata = this.video.getFrameData();
|
||||
var iofs = 0;
|
||||
var debugCond = this.getDebugCallback();
|
||||
var rgb;
|
||||
var mariaClocks = colorClocksPreDMA; // 7 CPU cycles until DMA
|
||||
// visible lines
|
||||
for (var sl=0; sl<linesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
var visible = sl < numVisibleLines;
|
||||
this.maria.setVBLANK(!visible);
|
||||
// iterate CPU with free clocks
|
||||
while (mariaClocks > 0) {
|
||||
// wait for WSYNC? (end of line)
|
||||
if (this.maria.WSYNC) {
|
||||
if (mariaClocks >= colorClocksPreDMA) {
|
||||
this.maria.WSYNC--;
|
||||
mariaClocks = colorClocksPreDMA; // 7 CPU cycles until DMA
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// next CPU clock
|
||||
mariaClocks -= 4;
|
||||
if (debugCond && debugCond()) {
|
||||
debugCond = null;
|
||||
sl = 999;
|
||||
break;
|
||||
}
|
||||
this.cpu.clockPulse();
|
||||
}
|
||||
mariaClocks += colorClocksPerLine;
|
||||
// is this scanline visible?
|
||||
if (visible) {
|
||||
// do DMA for scanline?
|
||||
mariaClocks -= this.maria.doDMA(this);
|
||||
// copy line to frame buffer
|
||||
for (var i=0; i<320; i++) {
|
||||
idata[iofs++] = COLORS_RGBA[this.maria.pixels[i]];
|
||||
}
|
||||
}
|
||||
// do interrupt? (if visible or before 1st scanline)
|
||||
if ((visible || sl == linesPerFrame-1) && this.maria.doInterrupt()) {
|
||||
this.profiler && this.profiler.logInterrupt(0);
|
||||
mariaClocks -= this.cpu.setNMIAndWait() * 4;
|
||||
}
|
||||
}
|
||||
// update video frame
|
||||
if (!novideo) {
|
||||
this.video.updateFrame();
|
||||
// set background/border color
|
||||
let bkcol = this.maria.regs[0x0];
|
||||
$(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
|
||||
}
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
if (data.length == 0xc080) data = data.slice(0x80); // strip header
|
||||
this.rom = padBytes(data, romLength, true);
|
||||
this.reset();
|
||||
}
|
||||
/*
|
||||
loadBIOS(title, data) {
|
||||
this.bios = padBytes(data, 0x1000);
|
||||
this.reset();
|
||||
}
|
||||
*/
|
||||
isRunning() {
|
||||
return this.timer.isRunning();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
this.audio.stop();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.timer.start();
|
||||
this.audio.start();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
this.tia.reset();
|
||||
this.maria.reset();
|
||||
this.inputs.fill(0x0);
|
||||
this.inputs[SWCHA] = 0xff;
|
||||
this.inputs[SWCHB] = 1+2+8;
|
||||
this.cpu.clockPulse(); // TODO: needed for test to pass?
|
||||
}
|
||||
|
||||
// TODO: don't log if profiler active
|
||||
readAddress(addr : number) {
|
||||
return this.bus.read(addr) | 0;
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.unfixPC(state.c);
|
||||
this.cpu.loadState(state.c);
|
||||
this.fixPC(state.c);
|
||||
this.ram.set(state.b);
|
||||
this.tia.loadState(state.tia);
|
||||
this.maria.loadState(state.maria);
|
||||
this.loadControlsState(state);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:this.ram.slice(0),
|
||||
tia:this.tia.saveState(),
|
||||
maria:this.maria.saveState(),
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.in);
|
||||
}
|
||||
|
||||
saveControlsState() {
|
||||
return {
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
getCPUState() {
|
||||
return this.fixPC(this.cpu.saveState());
|
||||
}
|
||||
|
||||
getRasterScanline() {
|
||||
return this.scanline;
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return ['CPU','Stack','TIA','MARIA'];
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'TIA': return TIA.stateToLongString(state.tia);
|
||||
case 'MARIA': return MARIA.stateToLongString(state.maria) + "\nScanline: " + this.scanline;
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
const ATARI_NTSC_RGB = [
|
||||
0x000000, // 00
|
||||
0x404040, // 02
|
||||
0x6c6c6c, // 04
|
||||
0x909090, // 06
|
||||
0xb0b0b0, // 08
|
||||
0xc8c8c8, // 0A
|
||||
0xdcdcdc, // 0C
|
||||
0xf4f4f4, // 0E
|
||||
0x004444, // 10
|
||||
0x106464, // 12
|
||||
0x248484, // 14
|
||||
0x34a0a0, // 16
|
||||
0x40b8b8, // 18
|
||||
0x50d0d0, // 1A
|
||||
0x5ce8e8, // 1C
|
||||
0x68fcfc, // 1E
|
||||
0x002870, // 20
|
||||
0x144484, // 22
|
||||
0x285c98, // 24
|
||||
0x3c78ac, // 26
|
||||
0x4c8cbc, // 28
|
||||
0x5ca0cc, // 2A
|
||||
0x68b4dc, // 2C
|
||||
0x78c8ec, // 2E
|
||||
0x001884, // 30
|
||||
0x183498, // 32
|
||||
0x3050ac, // 34
|
||||
0x4868c0, // 36
|
||||
0x5c80d0, // 38
|
||||
0x7094e0, // 3A
|
||||
0x80a8ec, // 3C
|
||||
0x94bcfc, // 3E
|
||||
0x000088, // 40
|
||||
0x20209c, // 42
|
||||
0x3c3cb0, // 44
|
||||
0x5858c0, // 46
|
||||
0x7070d0, // 48
|
||||
0x8888e0, // 4A
|
||||
0xa0a0ec, // 4C
|
||||
0xb4b4fc, // 4E
|
||||
0x5c0078, // 50
|
||||
0x74208c, // 52
|
||||
0x883ca0, // 54
|
||||
0x9c58b0, // 56
|
||||
0xb070c0, // 58
|
||||
0xc084d0, // 5A
|
||||
0xd09cdc, // 5C
|
||||
0xe0b0ec, // 5E
|
||||
0x780048, // 60
|
||||
0x902060, // 62
|
||||
0xa43c78, // 64
|
||||
0xb8588c, // 66
|
||||
0xcc70a0, // 68
|
||||
0xdc84b4, // 6A
|
||||
0xec9cc4, // 6C
|
||||
0xfcb0d4, // 6E
|
||||
0x840014, // 70
|
||||
0x982030, // 72
|
||||
0xac3c4c, // 74
|
||||
0xc05868, // 76
|
||||
0xd0707c, // 78
|
||||
0xe08894, // 7A
|
||||
0xeca0a8, // 7C
|
||||
0xfcb4bc, // 7E
|
||||
0x880000, // 80
|
||||
0x9c201c, // 82
|
||||
0xb04038, // 84
|
||||
0xc05c50, // 86
|
||||
0xd07468, // 88
|
||||
0xe08c7c, // 8A
|
||||
0xeca490, // 8C
|
||||
0xfcb8a4, // 8E
|
||||
0x7c1800, // 90
|
||||
0x90381c, // 92
|
||||
0xa85438, // 94
|
||||
0xbc7050, // 96
|
||||
0xcc8868, // 98
|
||||
0xdc9c7c, // 9A
|
||||
0xecb490, // 9C
|
||||
0xfcc8a4, // 9E
|
||||
0x5c2c00, // A0
|
||||
0x784c1c, // A2
|
||||
0x906838, // A4
|
||||
0xac8450, // A6
|
||||
0xc09c68, // A8
|
||||
0xd4b47c, // AA
|
||||
0xe8cc90, // AC
|
||||
0xfce0a4, // AE
|
||||
0x2c3c00, // B0
|
||||
0x485c1c, // B2
|
||||
0x647c38, // B4
|
||||
0x809c50, // B6
|
||||
0x94b468, // B8
|
||||
0xacd07c, // BA
|
||||
0xc0e490, // BC
|
||||
0xd4fca4, // BE
|
||||
0x003c00, // C0
|
||||
0x205c20, // C2
|
||||
0x407c40, // C4
|
||||
0x5c9c5c, // C6
|
||||
0x74b474, // C8
|
||||
0x8cd08c, // CA
|
||||
0xa4e4a4, // CC
|
||||
0xb8fcb8, // CE
|
||||
0x003814, // D0
|
||||
0x1c5c34, // D2
|
||||
0x387c50, // D4
|
||||
0x50986c, // D6
|
||||
0x68b484, // D8
|
||||
0x7ccc9c, // DA
|
||||
0x90e4b4, // DC
|
||||
0xa4fcc8, // DE
|
||||
0x00302c, // E0
|
||||
0x1c504c, // E2
|
||||
0x347068, // E4
|
||||
0x4c8c84, // E6
|
||||
0x64a89c, // E8
|
||||
0x78c0b4, // EA
|
||||
0x88d4cc, // EC
|
||||
0x9cece0, // EE
|
||||
0x002844, // F0
|
||||
0x184864, // F2
|
||||
0x306884, // F4
|
||||
0x4484a0, // F6
|
||||
0x589cb8, // F8
|
||||
0x6cb4d0, // FA
|
||||
0x7ccce8, // FC
|
||||
0x8ce0fc // FE
|
||||
];
|
||||
|
||||
var COLORS_RGBA = new Uint32Array(256);
|
||||
var COLORS_WEB = [];
|
||||
for (var i=0; i<256; i++) {
|
||||
COLORS_RGBA[i] = ATARI_NTSC_RGB[i>>1] | 0xff000000;
|
||||
COLORS_WEB[i] = "#"+hex(rgb2bgr(ATARI_NTSC_RGB[i>>1]),6);
|
||||
newMachine() { return new Atari7800(); }
|
||||
getPresets() { return Atari7800_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.readConst(a); }
|
||||
// TODO loadBios(bios) { this.machine.loadBIOS(a); }
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'TIA',start:0x00,size:0x20,type:'io'},
|
||||
{name:'MARIA',start:0x20,size:0x20,type:'io'},
|
||||
{name:'RAM (6166 Block 0)',start:0x40,size:0xc0,type:'ram'},
|
||||
{name:'RAM (6166 Block 1)',start:0x140,size:0xc0,type:'ram'},
|
||||
{name:'PIA',start:0x280,size:0x18,type:'io'},
|
||||
{name:'RAM',start:0x1800,size:0x1000,type:'ram'}, // TODO: shadow ram
|
||||
{name:'Cartridge ROM',start:0x4000,size:0xc000,type:'rom'},
|
||||
] } };
|
||||
}
|
||||
|
||||
///
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,28 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BaseMAMEPlatform, BasicZ80ScanlinePlatform, getToolForFilename_z80 } from "../baseplatform";
|
||||
import { PLATFORMS, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { MasterAudio, SN76489_Audio } from "../audio";
|
||||
import { TMS9918A } from "../video/tms9918a";
|
||||
|
||||
// http://www.colecovision.eu/ColecoVision/development/tutorial1.shtml
|
||||
// http://www.colecovision.eu/ColecoVision/development/libcv.shtml
|
||||
// http://www.kernelcrash.com/blog/recreating-the-colecovision/2016/01/27/
|
||||
// http://www.atarihq.com/danb/files/CV-Tech.txt
|
||||
// http://www.atarihq.com/danb/files/CV-Sound.txt
|
||||
// http://www.colecoboxart.com/faq/FAQ05.htm
|
||||
// http://www.theadamresource.com/manuals/technical/Jeffcoleco.html
|
||||
// http://bifi.msxnet.org/msxnet//tech/tms9918a.txt
|
||||
// http://www.colecovision.dk/tools.htm?refreshed
|
||||
// http://www.theadamresource.com/manuals/technical/ColecoVision%20Coding%20Guide.pdf
|
||||
// http://www.unige.ch/medecine/nouspikel/ti99/tms9918a.htm
|
||||
// http://map.grauw.nl/articles/vdp_tut.php
|
||||
// http://www.msxcomputermagazine.nl/mccw/91/msx1demos1/en.html
|
||||
// http://www.segordon.com/colecovision.php
|
||||
// http://samdal.com/svvideo.htm
|
||||
// https://github.com/tursilion/convert9918
|
||||
// http://www.harmlesslion.com/cgi-bin/showprog.cgi?ColecoVision
|
||||
import { ColecoVision } from "../machine/coleco";
|
||||
import { Platform, BaseZ80MachinePlatform, BaseMAMEPlatform, getToolForFilename_z80 } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
export var ColecoVision_PRESETS = [
|
||||
{ id: 'text.c', name: 'Text Mode' },
|
||||
|
@ -41,194 +20,20 @@ export var ColecoVision_PRESETS = [
|
|||
{ id: 'climber.c', name: 'Platform Game' },
|
||||
];
|
||||
|
||||
var COLECOVISION_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x4],
|
||||
[Keys.LEFT, 0, 0x8],
|
||||
[Keys.RIGHT, 0, 0x2],
|
||||
[Keys.A, 0, 0x40],
|
||||
[Keys.B, 1, 0x40],
|
||||
class ColecoVisionPlatform extends BaseZ80MachinePlatform<ColecoVision> implements Platform {
|
||||
|
||||
[Keys.P2_UP, 2, 0x1],
|
||||
[Keys.P2_DOWN, 2, 0x4],
|
||||
[Keys.P2_LEFT, 2, 0x8],
|
||||
[Keys.P2_RIGHT, 2, 0x2],
|
||||
[Keys.P2_A, 2, 0x40],
|
||||
[Keys.P2_B, 3, 0x40],
|
||||
]);
|
||||
|
||||
class ColecoVisionPlatform extends BasicZ80ScanlinePlatform implements Platform {
|
||||
|
||||
cpuFrequency = 3579545; // MHz
|
||||
canvasWidth = 304;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 240;
|
||||
defaultROMSize = 0x8000;
|
||||
|
||||
vdp: TMS9918A;
|
||||
bios: Uint8Array;
|
||||
keypadMode: boolean;
|
||||
audio;
|
||||
psg;
|
||||
|
||||
getPresets() { return ColecoVision_PRESETS; }
|
||||
|
||||
getKeyboardMap() { return COLECOVISION_KEYCODE_MAP; }
|
||||
|
||||
getVideoOptions() { return { overscan: true }; }
|
||||
|
||||
newRAM() {
|
||||
return new Uint8Array(0x400);
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x1fff, 0x1fff, (a) => { return this.bios ? this.bios[a] : 0; }],
|
||||
[0x6000, 0x7fff, 0x3ff, (a) => { return this.ram[a]; }],
|
||||
[0x8000, 0xffff, 0x7fff, (a) => { return this.rom ? this.rom[a] : 0; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x6000, 0x7fff, 0x3ff, (a, v) => { this.ram[a] = v; }],
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr:number):number => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0xfc: return this.inputs[this.keypadMode ? 1 : 0] ^ 0xff;
|
||||
case 0xff: return this.inputs[this.keypadMode ? 3 : 2] ^ 0xff;
|
||||
}
|
||||
if (addr >= 0xa0 && addr <= 0xbf) {
|
||||
if (addr & 1)
|
||||
return this.vdp.readStatus();
|
||||
else
|
||||
return this.vdp.readData();
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr >> 4) {
|
||||
case 0x8: case 0x9: this.keypadMode = true; break;
|
||||
case 0xc: case 0xd: this.keypadMode = false; break;
|
||||
case 0xa: case 0xb:
|
||||
if (addr & 1)
|
||||
return this.vdp.writeAddress(val);
|
||||
else
|
||||
return this.vdp.writeData(val);
|
||||
case 0xf: this.psg.setData(val); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(COLECO_BIOS_LZG)));
|
||||
this.audio = new MasterAudio();
|
||||
this.psg = new SN76489_Audio(this.audio);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
this.cpu.nonMaskableInterrupt();
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
}
|
||||
};
|
||||
this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new TMS9918A(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
startScanline(sl: number) {
|
||||
}
|
||||
|
||||
drawScanline(sl: number) {
|
||||
this.vdp.drawScanline(sl);
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.vdp.restoreState(state['vdp']);
|
||||
this.keypadMode = state['kpm'];
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['vdp'] = this.vdp.getState();
|
||||
state['kpm'] = this.keypadMode;
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.vdp.reset();
|
||||
this.psg.reset();
|
||||
this.keypadMode = false;
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return super.getDebugCategories().concat(['VDP']);
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
vdpStateToLongString(ppu) {
|
||||
return this.vdp.getRegsString();
|
||||
}
|
||||
readVRAMAddress(a : number) : number {
|
||||
return this.vdp.ram[a & 0x3fff];
|
||||
}
|
||||
newMachine() { return new ColecoVision(); }
|
||||
getPresets() { return ColecoVision_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
readVRAMAddress(a) { return this.machine.readVRAMAddress(a); }
|
||||
// TODO loadBios(bios) { this.machine.loadBIOS(a); }
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'BIOS',start:0x0,size:0x2000,type:'rom'},
|
||||
{name:'Cartridge Header',start:0x8000,size:0x100,type:'rom'},
|
||||
] } };
|
||||
}
|
||||
|
||||
var COLECO_BIOS_LZG = `
|
||||
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`;
|
||||
|
||||
/// MAME support
|
||||
|
||||
class ColecoVisionMAMEPlatform extends BaseMAMEPlatform implements Platform {
|
||||
|
|
|
@ -1,162 +1,143 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BaseZ80Platform } from "../baseplatform";
|
||||
import { Platform, BaseZ80Platform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex } from "../util";
|
||||
import { MasterAudio, AY38910_Audio } from "../audio";
|
||||
|
||||
const GALAXIAN_PRESETS = [
|
||||
{id:'gfxtest.c', name:'Graphics Test'},
|
||||
{id:'shoot2.c', name:'Solarian Game'},
|
||||
{ id: 'gfxtest.c', name: 'Graphics Test' },
|
||||
{ id: 'shoot2.c', name: 'Solarian Game' },
|
||||
];
|
||||
|
||||
const GALAXIAN_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 0, 0x10], // P1
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.A, 0, 0x10], // P1
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.P2_A, 1, 0x10], // P2
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_A, 1, 0x10], // P2
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_RIGHT, 1, 0x8],
|
||||
[Keys.SELECT, 0, 0x1],
|
||||
[Keys.START, 1, 0x1],
|
||||
[Keys.START, 1, 0x1],
|
||||
[Keys.VK_2, 1, 0x2],
|
||||
]);
|
||||
|
||||
const SCRAMBLE_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, -0x1], // P1
|
||||
[Keys.B, 0, -0x2], // fire
|
||||
[Keys.VK_7, 0, -0x4], // credit
|
||||
[Keys.A, 0, -0x8], // bomb
|
||||
[Keys.RIGHT, 0, -0x10],
|
||||
[Keys.LEFT, 0, -0x20],
|
||||
[Keys.VK_6, 0, -0x40],
|
||||
[Keys.SELECT, 0, -0x80],
|
||||
[Keys.START, 1, -0x80],
|
||||
[Keys.VK_2, 1, -0x40],
|
||||
[Keys.DOWN, 2, -0x40],
|
||||
[Keys.UP, 0, -0x1], // P1
|
||||
[Keys.B, 0, -0x2], // fire
|
||||
[Keys.VK_7, 0, -0x4], // credit
|
||||
[Keys.A, 0, -0x8], // bomb
|
||||
[Keys.RIGHT, 0, -0x10],
|
||||
[Keys.LEFT, 0, -0x20],
|
||||
[Keys.VK_6, 0, -0x40],
|
||||
[Keys.SELECT, 0, -0x80],
|
||||
[Keys.START, 1, -0x80],
|
||||
[Keys.VK_2, 1, -0x40],
|
||||
[Keys.DOWN, 2, -0x40],
|
||||
//[Keys.VK_UP, 2, -0x10],
|
||||
]);
|
||||
|
||||
const GalaxianVideo = function(rom:Uint8Array, vram:RAM, oram:RAM, palette:Uint32Array, options) {
|
||||
|
||||
const _GalaxianPlatform = function(mainElement, options) {
|
||||
options = options || {};
|
||||
var romSize = options.romSize || 0x4000;
|
||||
var gfxBase = options.gfxBase || 0x2800;
|
||||
var palBase = options.palBase || 0x3800;
|
||||
var keyMap = options.keyMap || GALAXIAN_KEYCODE_MAP;
|
||||
var missileWidth = options.missileWidth || 4;
|
||||
var missileOffset = options.missileOffset || 0;
|
||||
|
||||
var cpu;
|
||||
var ram, vram, oram : RAM;
|
||||
var membus, iobus, rom, palette, outlatches;
|
||||
var video, audio, timer, pixels;
|
||||
var psg1, psg2;
|
||||
var inputs;
|
||||
var interruptEnabled = 0;
|
||||
var starsEnabled = 0;
|
||||
var watchdog_counter;
|
||||
var frameCounter = 0;
|
||||
|
||||
var XTAL = 18432000.0;
|
||||
var scanlinesPerFrame = 264;
|
||||
var cpuFrequency = XTAL/6; // 3.072 MHz
|
||||
var hsyncFrequency = XTAL/3/192/2; // 16 kHz
|
||||
var vsyncFrequency = hsyncFrequency/132/2; // 60.606060 Hz
|
||||
var vblankDuration = 1/vsyncFrequency * (20/132); // 2500 us
|
||||
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
|
||||
var INITIAL_WATCHDOG = 8;
|
||||
var showOffscreenObjects = false;
|
||||
this.missileWidth = options.missileWidth || 4;
|
||||
this.missileOffset = options.missileOffset || 0;
|
||||
this.showOffscreenObjects = false;
|
||||
this.frameCounter = 0;
|
||||
this.starsEnabled = 0;
|
||||
var stars = [];
|
||||
for (var i=0; i<256; i++)
|
||||
for (var i = 0; i < 256; i++)
|
||||
stars[i] = noise();
|
||||
|
||||
function drawScanline(pixels, sl) {
|
||||
if (sl < 16 && !showOffscreenObjects) return; // offscreen
|
||||
if (sl >= 240 && !showOffscreenObjects) return; // offscreen
|
||||
this.advanceFrame = function() {
|
||||
this.frameCounter = (this.frameCounter + 1) & 0xff;
|
||||
}
|
||||
|
||||
this.drawScanline = function(pixels, sl) {
|
||||
if (sl < 16 && !this.showOffscreenObjects) return; // offscreen
|
||||
if (sl >= 240 && !this.showOffscreenObjects) return; // offscreen
|
||||
// draw tiles
|
||||
var pixofs = sl*264;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
for (var xx=0; xx<32; xx++) {
|
||||
var pixofs = sl * 264;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
for (var xx = 0; xx < 32; xx++) {
|
||||
var xofs = xx;
|
||||
var scroll = oram.mem[xofs*2]; // even entries control scroll position
|
||||
var attrib = oram.mem[xofs*2+1]; // odd entries control the color base
|
||||
var scroll = oram.mem[xofs * 2]; // even entries control scroll position
|
||||
var attrib = oram.mem[xofs * 2 + 1]; // odd entries control the color base
|
||||
var sl2 = (sl + scroll) & 0xff;
|
||||
var vramofs = (sl2>>3)<<5; // offset in VRAM
|
||||
var vramofs = (sl2 >> 3) << 5; // offset in VRAM
|
||||
var yy = sl2 & 7; // y offset within tile
|
||||
var tile = vram.mem[vramofs+xofs]; // TODO: why undefined?
|
||||
var color0 = (attrib & 7) << 2;
|
||||
var addr = gfxBase+(tile<<3)+yy;
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = color0 + ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var tile = vram.mem[vramofs + xofs]; // TODO: why undefined?
|
||||
var color0 = (attrib & 7) << 2;
|
||||
var addr = gfxBase + (tile << 3) + yy;
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr + 0x800];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = color0 + ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
pixels[outi] = palette[color];
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// draw sprites
|
||||
for (var sprnum=7; sprnum>=0; sprnum--) {
|
||||
var base = (sprnum<<2) + 0x40;
|
||||
for (var sprnum = 7; sprnum >= 0; sprnum--) {
|
||||
var base = (sprnum << 2) + 0x40;
|
||||
var base0 = oram.mem[base];
|
||||
var sy = 240 - (base0 - ((sprnum<3)?1:0)); // the first three sprites match against y-1
|
||||
var sy = 240 - (base0 - ((sprnum < 3) ? 1 : 0)); // the first three sprites match against y-1
|
||||
var yy = (sl - sy);
|
||||
if (yy >= 0 && yy < 16) {
|
||||
var sx = oram.mem[base+3] + 1; // +1 pixel offset from tiles
|
||||
if (sx == 0 && !showOffscreenObjects)
|
||||
var sx = oram.mem[base + 3] + 1; // +1 pixel offset from tiles
|
||||
if (sx == 0 && !this.showOffscreenObjects)
|
||||
continue; // drawn off-buffer
|
||||
var code = oram.mem[base+1];
|
||||
var code = oram.mem[base + 1];
|
||||
var flipx = code & 0x40; // TODO: flipx
|
||||
if (code & 0x80) // flipy
|
||||
yy = 15-yy;
|
||||
yy = 15 - yy;
|
||||
code &= 0x3f;
|
||||
var color0 = (oram.mem[base+2] & 7) << 2;
|
||||
var addr = gfxBase+(code<<5)+(yy<8?yy:yy+8);
|
||||
var color0 = (oram.mem[base + 2] & 7) << 2;
|
||||
var addr = gfxBase + (code << 5) + (yy < 8 ? yy : yy + 8);
|
||||
outi = pixofs + sx; //<< 1
|
||||
var data1 = rom[addr];
|
||||
var data2 = rom[addr+0x800];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var data2 = rom[addr + 0x800];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
if (color)
|
||||
pixels[flipx?(outi+15-i):(outi+i)] = palette[color0 + color];
|
||||
pixels[flipx ? (outi + 15 - i) : (outi + i)] = palette[color0 + color];
|
||||
}
|
||||
var data1 = rom[addr+8];
|
||||
var data2 = rom[addr+0x808];
|
||||
for (var i=0; i<8; i++) {
|
||||
var bm = 128>>i;
|
||||
var color = ((data1&bm)?1:0) + ((data2&bm)?2:0);
|
||||
var data1 = rom[addr + 8];
|
||||
var data2 = rom[addr + 0x808];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
var color = ((data1 & bm) ? 1 : 0) + ((data2 & bm) ? 2 : 0);
|
||||
if (color)
|
||||
pixels[flipx?(outi+7-i):(outi+i+8)] = palette[color0 + color];
|
||||
pixels[flipx ? (outi + 7 - i) : (outi + i + 8)] = palette[color0 + color];
|
||||
}
|
||||
}
|
||||
}
|
||||
// draw bullets/shells
|
||||
var shell = 0xff;
|
||||
var missile = 0xff;
|
||||
for (var which=0; which<8; which++) {
|
||||
var sy = oram.mem[0x60 + (which<<2)+1];
|
||||
if (((sy + sl - ((which<3)?1:0))&0xff) == 0xff) {
|
||||
for (var which = 0; which < 8; which++) {
|
||||
var sy = oram.mem[0x60 + (which << 2) + 1];
|
||||
if (((sy + sl - ((which < 3) ? 1 : 0)) & 0xff) == 0xff) {
|
||||
if (which != 7)
|
||||
shell = which;
|
||||
else
|
||||
missile = which;
|
||||
}
|
||||
}
|
||||
for (var i=0; i<2; i++) {
|
||||
for (var i = 0; i < 2; i++) {
|
||||
which = i ? missile : shell;
|
||||
if (which != 0xff) {
|
||||
var sx = 255 - oram.mem[0x60 + (which<<2)+3];
|
||||
var outi = pixofs+sx-missileOffset;
|
||||
var sx = 255 - oram.mem[0x60 + (which << 2) + 3];
|
||||
var outi = pixofs + sx - this.missileOffset;
|
||||
var col = which == 7 ? 0xffffff00 : 0xffffffff;
|
||||
for (var j=0; j<missileWidth; j++)
|
||||
for (var j = 0; j < this.missileWidth; j++)
|
||||
pixels[outi++] = col;
|
||||
}
|
||||
}
|
||||
// draw stars
|
||||
if (starsEnabled) {
|
||||
var starx = ((frameCounter + stars[sl & 0xff]) & 0xff);
|
||||
if (this.starsEnabled) {
|
||||
var starx = ((this.frameCounter + stars[sl & 0xff]) & 0xff);
|
||||
if ((starx + sl) & 0x10) {
|
||||
var outi = pixofs + starx;
|
||||
if ((pixels[outi] & 0xffffff) == 0) {
|
||||
|
@ -164,11 +145,38 @@ const _GalaxianPlatform = function(mainElement, options) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _GalaxianPlatform = function(mainElement, options) {
|
||||
options = options || {};
|
||||
var romSize = options.romSize || 0x4000;
|
||||
var palBase = options.palBase || 0x3800;
|
||||
var keyMap = options.keyMap || GALAXIAN_KEYCODE_MAP;
|
||||
|
||||
var cpu;
|
||||
var ram, vram, oram: RAM;
|
||||
var membus, iobus, rom, palette, outlatches;
|
||||
var video, audio, timer, pixels;
|
||||
var psg1, psg2;
|
||||
var inputs;
|
||||
var interruptEnabled = 0;
|
||||
var watchdog_counter;
|
||||
|
||||
var XTAL = 18432000.0;
|
||||
var scanlinesPerFrame = 264;
|
||||
var cpuFrequency = XTAL / 6; // 3.072 MHz
|
||||
var hsyncFrequency = XTAL / 3 / 192 / 2; // 16 kHz
|
||||
var vsyncFrequency = hsyncFrequency / 132 / 2; // 60.606060 Hz
|
||||
var vblankDuration = 1 / vsyncFrequency * (20 / 132); // 2500 us
|
||||
var cpuCyclesPerLine = cpuFrequency / hsyncFrequency;
|
||||
var INITIAL_WATCHDOG = 8;
|
||||
var gfx; // = new GalaxianVideo(rom, vram, oram, palette, options);
|
||||
|
||||
var m_protection_state = 0;
|
||||
var m_protection_result = 0;
|
||||
function scramble_protection_w(addr,data) {
|
||||
function scramble_protection_w(addr, data) {
|
||||
/*
|
||||
This is not fully understood; the low 4 bits of port C are
|
||||
inputs; the upper 4 bits are outputs. Scramble main set always
|
||||
|
@ -176,237 +184,243 @@ const _GalaxianPlatform = function(mainElement, options) {
|
|||
expects certain results in the upper nibble afterwards.
|
||||
*/
|
||||
m_protection_state = (m_protection_state << 4) | (data & 0x0f);
|
||||
switch (m_protection_state & 0xfff)
|
||||
{
|
||||
/* scramble */
|
||||
case 0xf09: m_protection_result = 0xff; break;
|
||||
case 0xa49: m_protection_result = 0xbf; break;
|
||||
case 0x319: m_protection_result = 0x4f; break;
|
||||
case 0x5c9: m_protection_result = 0x6f; break;
|
||||
switch (m_protection_state & 0xfff) {
|
||||
/* scramble */
|
||||
case 0xf09: m_protection_result = 0xff; break;
|
||||
case 0xa49: m_protection_result = 0xbf; break;
|
||||
case 0x319: m_protection_result = 0x4f; break;
|
||||
case 0x5c9: m_protection_result = 0x6f; break;
|
||||
|
||||
/* scrambls */
|
||||
case 0x246: m_protection_result ^= 0x80; break;
|
||||
case 0xb5f: m_protection_result = 0x6f; break;
|
||||
/* scrambls */
|
||||
case 0x246: m_protection_result ^= 0x80; break;
|
||||
case 0xb5f: m_protection_result = 0x6f; break;
|
||||
}
|
||||
}
|
||||
function scramble_protection_alt_r() {
|
||||
var bit = (m_protection_result >> 7) & 1;
|
||||
return (bit << 5) | ((bit^1) << 7);
|
||||
}
|
||||
|
||||
const bitcolors = [
|
||||
0x000021, 0x000047, 0x000097, // red
|
||||
0x002100, 0x004700, 0x009700, // green
|
||||
0x510000, 0xae0000 // blue
|
||||
];
|
||||
|
||||
class GalaxianPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline : number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return GALAXIAN_PRESETS;
|
||||
return (bit << 5) | ((bit ^ 1) << 7);
|
||||
}
|
||||
|
||||
start() {
|
||||
ram = new RAM(0x800);
|
||||
vram = new RAM(0x400);
|
||||
oram = new RAM(0x100);
|
||||
outlatches = new RAM(0x8);
|
||||
if (options.scramble) {
|
||||
inputs = [0xff,0xfc,0xf1];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x7ff, function(a) { return ram.mem[a]; }],
|
||||
// [0x4800, 0x4fff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
// [0x5000, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
//[0x8000, 0x820f, 0, function(a) { return noise(); }], // TODO: remove
|
||||
[0x8100, 0x8100, 0, function(a) { return inputs[0]; }],
|
||||
[0x8101, 0x8101, 0, function(a) { return inputs[1]; }],
|
||||
[0x8102, 0x8102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x8202, 0x8202, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
[0x9100, 0x9100, 0, function(a) { return inputs[0]; }],
|
||||
[0x9101, 0x9101, 0, function(a) { return inputs[1]; }],
|
||||
[0x9102, 0x9102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x9212, 0x9212, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
//[0, 0xffff, 0, function(a) { console.log(hex(a)); return 0; }]
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x7ff, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x4800, 0x4fff, 0x3ff, function(a,v) { vram.mem[a] = v; }],
|
||||
[0x5000, 0x5fff, 0xff, function(a,v) { oram.mem[a] = v; }],
|
||||
[0x6801, 0x6801, 0, function(a,v) { interruptEnabled = v & 1; /*console.log(a,v,cpu.getPC().toString(16));*/ }],
|
||||
[0x6802, 0x6802, 0, function(a,v) { /* TODO: coin counter */ }],
|
||||
[0x6803, 0x6803, 0, function(a,v) { /* TODO: backgroundColor = (v & 1) ? 0xFF000056 : 0xFF000000; */ }],
|
||||
[0x6804, 0x6804, 0, function(a,v) { starsEnabled = v & 1; }],
|
||||
[0x6808, 0x6808, 0, function(a,v) { missileWidth = v; }], // not on h/w
|
||||
[0x6809, 0x6809, 0, function(a,v) { missileOffset = v; }], // not on h/w
|
||||
[0x8202, 0x8202, 0, scramble_protection_w],
|
||||
//[0x8100, 0x8103, 0, function(a,v){ /* PPI 0 */ }],
|
||||
//[0x8200, 0x8203, 0, function(a,v){ /* PPI 1 */ }],
|
||||
//[0, 0xffff, 0, function(a,v) { console.log(hex(a),hex(v)); }]
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
inputs = [0xe,0x8,0x0];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x3ff, function(a) { return ram.mem[a]; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x6000, 0x6000, 0, function(a) { return inputs[0]; }],
|
||||
[0x6800, 0x6800, 0, function(a) { return inputs[1]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { return inputs[2]; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a,v) { vram.mem[a] = v; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a,v) { oram.mem[a] = v; }],
|
||||
//[0x6004, 0x6007, 0x3, function(a,v) { }], // lfo freq
|
||||
//[0x6800, 0x6807, 0x7, function(a,v) { }], // sound
|
||||
//[0x7800, 0x7800, 0x7, function(a,v) { }], // pitch
|
||||
[0x6000, 0x6003, 0x3, function(a,v) { outlatches.mem[a] = v; }],
|
||||
[0x7001, 0x7001, 0, function(a,v) { interruptEnabled = v & 1; }],
|
||||
[0x7004, 0x7004, 0, function(a,v) { starsEnabled = v & 1; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
const bitcolors = [
|
||||
0x000021, 0x000047, 0x000097, // red
|
||||
0x002100, 0x004700, 0x009700, // green
|
||||
0x510000, 0xae0000 // blue
|
||||
];
|
||||
|
||||
class GalaxianPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline: number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return GALAXIAN_PRESETS;
|
||||
}
|
||||
audio = new MasterAudio();
|
||||
psg1 = new AY38910_Audio(audio);
|
||||
psg2 = new AY38910_Audio(audio);
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return 0;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
if (addr & 0x1) { psg1.selectRegister(val & 0xf); };
|
||||
if (addr & 0x2) { psg1.setData(val); };
|
||||
if (addr & 0x4) { psg2.selectRegister(val & 0xf); };
|
||||
if (addr & 0x8) { psg2.setData(val); };
|
||||
}
|
||||
};
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
video = new RasterVideo(mainElement,264,264,{rotate:90});
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
this.poller = setKeyboardFromMap(video, inputs, keyMap);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
readAddress(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
for (var sl=0; sl<scanlinesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
if (!novideo) {
|
||||
drawScanline(pixels, sl);
|
||||
start() {
|
||||
ram = new RAM(0x800);
|
||||
vram = new RAM(0x400);
|
||||
oram = new RAM(0x100);
|
||||
rom = new Uint8Array(romSize);
|
||||
palette = new Uint32Array(new ArrayBuffer(32 * 4));
|
||||
gfx = new GalaxianVideo(rom, vram, oram, palette, options);
|
||||
|
||||
outlatches = new RAM(0x8);
|
||||
if (options.scramble) {
|
||||
inputs = [0xff, 0xfc, 0xf1];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x7ff, function(a) { return ram.mem[a]; }],
|
||||
[0x4800, 0x4fff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
[0x5000, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
//[0x8000, 0x820f, 0, function(a) { return noise(); }], // TODO: remove
|
||||
[0x8100, 0x8100, 0, function(a) { return inputs[0]; }],
|
||||
[0x8101, 0x8101, 0, function(a) { return inputs[1]; }],
|
||||
[0x8102, 0x8102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x8202, 0x8202, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
[0x9100, 0x9100, 0, function(a) { return inputs[0]; }],
|
||||
[0x9101, 0x9101, 0, function(a) { return inputs[1]; }],
|
||||
[0x9102, 0x9102, 0, function(a) { return inputs[2] | scramble_protection_alt_r(); }],
|
||||
[0x9212, 0x9212, 0, function(a) { return m_protection_result; }], // scramble (protection)
|
||||
//[0, 0xffff, 0, function(a) { console.log(hex(a)); return 0; }]
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x7ff, function(a, v) { ram.mem[a] = v; }],
|
||||
[0x4800, 0x4fff, 0x3ff, function(a, v) { vram.mem[a] = v; }],
|
||||
[0x5000, 0x5fff, 0xff, function(a, v) { oram.mem[a] = v; }],
|
||||
[0x6801, 0x6801, 0, function(a, v) { interruptEnabled = v & 1; /*console.log(a,v,cpu.getPC().toString(16));*/ }],
|
||||
[0x6802, 0x6802, 0, function(a, v) { /* TODO: coin counter */ }],
|
||||
[0x6803, 0x6803, 0, function(a, v) { /* TODO: backgroundColor = (v & 1) ? 0xFF000056 : 0xFF000000; */ }],
|
||||
[0x6804, 0x6804, 0, function(a, v) { gfx.starsEnabled = v & 1; }],
|
||||
[0x6808, 0x6808, 0, function(a, v) { gfx.missileWidth = v; }], // not on h/w
|
||||
[0x6809, 0x6809, 0, function(a, v) { gfx.missileOffset = v; }], // not on h/w
|
||||
[0x8202, 0x8202, 0, scramble_protection_w],
|
||||
//[0x8100, 0x8103, 0, function(a,v){ /* PPI 0 */ }],
|
||||
//[0x8200, 0x8203, 0, function(a,v){ /* PPI 1 */ }],
|
||||
//[0, 0xffff, 0, function(a,v) { console.log(hex(a),hex(v)); }]
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
inputs = [0xe, 0x8, 0x0];
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x47ff, 0x3ff, function(a) { return ram.mem[a]; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a) { return vram.mem[a]; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a) { return oram.mem[a]; }],
|
||||
[0x6000, 0x6000, 0, function(a) { return inputs[0]; }],
|
||||
[0x6800, 0x6800, 0, function(a) { return inputs[1]; }],
|
||||
[0x7000, 0x7000, 0, function(a) { return inputs[2]; }],
|
||||
[0x7800, 0x7800, 0, function(a) { watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x47ff, 0x3ff, function(a, v) { ram.mem[a] = v; }],
|
||||
[0x5000, 0x57ff, 0x3ff, function(a, v) { vram.mem[a] = v; }],
|
||||
[0x5800, 0x5fff, 0xff, function(a, v) { oram.mem[a] = v; }],
|
||||
//[0x6004, 0x6007, 0x3, function(a,v) { }], // lfo freq
|
||||
//[0x6800, 0x6807, 0x7, function(a,v) { }], // sound
|
||||
//[0x7800, 0x7800, 0x7, function(a,v) { }], // pitch
|
||||
[0x6000, 0x6003, 0x3, function(a, v) { outlatches.mem[a] = v; }],
|
||||
[0x7001, 0x7001, 0, function(a, v) { interruptEnabled = v & 1; }],
|
||||
[0x7004, 0x7004, 0, function(a, v) { gfx.starsEnabled = v & 1; }],
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
}
|
||||
this.runCPU(cpu, cpuCyclesPerLine);
|
||||
audio = new MasterAudio();
|
||||
psg1 = new AY38910_Audio(audio);
|
||||
psg2 = new AY38910_Audio(audio);
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return 0;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
if (addr & 0x1) { psg1.selectRegister(val & 0xf); };
|
||||
if (addr & 0x2) { psg1.setData(val); };
|
||||
if (addr & 0x4) { psg2.selectRegister(val & 0xf); };
|
||||
if (addr & 0x8) { psg2.setData(val); };
|
||||
}
|
||||
};
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
video = new RasterVideo(mainElement, 264, 264, { rotate: 90 });
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
this.poller = setKeyboardFromMap(video, inputs, keyMap);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
// visible area is 256x224 (before rotation)
|
||||
if (!novideo) {
|
||||
video.updateFrame(0, 0, 0, 0, showOffscreenObjects ? 264 : 256, 264);
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
readAddress(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
|
||||
}
|
||||
frameCounter = (frameCounter + 1) & 0xff;
|
||||
if (watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
|
||||
|
||||
advance(novideo: boolean) {
|
||||
for (var sl = 0; sl < scanlinesPerFrame; sl++) {
|
||||
this.scanline = sl;
|
||||
if (!novideo) {
|
||||
gfx.drawScanline(pixels, sl);
|
||||
}
|
||||
this.runCPU(cpu, cpuCyclesPerLine);
|
||||
}
|
||||
// visible area is 256x224 (before rotation)
|
||||
if (!novideo) {
|
||||
video.updateFrame(0, 0, 0, 0, gfx.showOffscreenObjects ? 264 : 256, 264);
|
||||
}
|
||||
gfx.advanceFrame();
|
||||
if (watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
|
||||
this.reset();
|
||||
}
|
||||
// NMI interrupt @ 0x66
|
||||
if (interruptEnabled) { cpu.NMI(); }
|
||||
}
|
||||
|
||||
getRasterScanline() { return this.scanline; }
|
||||
|
||||
loadROM(title, data) {
|
||||
rom.set(padBytes(data, romSize));
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
var b = rom[palBase + i];
|
||||
palette[i] = 0xff000000;
|
||||
for (var j = 0; j < 8; j++)
|
||||
if (((1 << j) & b))
|
||||
palette[i] += bitcolors[j];
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
// NMI interrupt @ 0x66
|
||||
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
|
||||
}
|
||||
|
||||
getRasterScanline() { return this.scanline; }
|
||||
|
||||
loadROM(title, data) {
|
||||
rom = padBytes(data, romSize);
|
||||
loadState(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vram.mem.set(state.bv);
|
||||
oram.mem.set(state.bo);
|
||||
watchdog_counter = state.wdc;
|
||||
interruptEnabled = state.ie;
|
||||
gfx.starsEnabled = state.se;
|
||||
gfx.frameCounter = state.fc;
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c: this.getCPUState(),
|
||||
b: ram.mem.slice(0),
|
||||
bv: vram.mem.slice(0),
|
||||
bo: oram.mem.slice(0),
|
||||
fc: gfx.frameCounter,
|
||||
ie: interruptEnabled,
|
||||
se: gfx.starsEnabled,
|
||||
wdc: watchdog_counter,
|
||||
in0: inputs[0],
|
||||
in1: inputs[1],
|
||||
in2: inputs[2],
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in0: inputs[0],
|
||||
in1: inputs[1],
|
||||
in2: inputs[2],
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
palette = new Uint32Array(new ArrayBuffer(32*4));
|
||||
for (var i=0; i<32; i++) {
|
||||
var b = rom[palBase+i];
|
||||
palette[i] = 0xff000000;
|
||||
for (var j=0; j<8; j++)
|
||||
if (((1<<j) & b))
|
||||
palette[i] += bitcolors[j];
|
||||
}
|
||||
this.reset();
|
||||
isRunning() {
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
resume() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
reset() {
|
||||
cpu.reset();
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'Video RAM',start:0x5000,size:0x400,type:'ram'},
|
||||
{name:'Sprite RAM',start:0x5800,size:0x100,type:'ram'},
|
||||
{name:'I/O Registers',start:0x6000,size:0x2000,type:'io'},
|
||||
] } };
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
vram.mem.set(state.bv);
|
||||
oram.mem.set(state.bo);
|
||||
watchdog_counter = state.wdc;
|
||||
interruptEnabled = state.ie;
|
||||
starsEnabled = state.se;
|
||||
frameCounter = state.fc;
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
bv:vram.mem.slice(0),
|
||||
bo:oram.mem.slice(0),
|
||||
fc:frameCounter,
|
||||
ie:interruptEnabled,
|
||||
se:starsEnabled,
|
||||
wdc:watchdog_counter,
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
inputs[0] = state.in0;
|
||||
inputs[1] = state.in1;
|
||||
inputs[2] = state.in2;
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in0:inputs[0],
|
||||
in1:inputs[1],
|
||||
in2:inputs[2],
|
||||
};
|
||||
}
|
||||
getCPUState() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
pause() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
resume() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
reset() {
|
||||
cpu.reset();
|
||||
//audio.reset();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
}
|
||||
|
||||
return new GalaxianPlatform();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,240 +1,27 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, getMousePos, EmuHalt, KeyFlags, _setKeyboardEvents } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray, lpad, rpad, rgb2bgr } from "../util";
|
||||
|
||||
declare var jt; // for 6502
|
||||
|
||||
// http://www.6502.org/trainers/buildkim/kim.htm
|
||||
// http://users.telenet.be/kim1-6502/
|
||||
// http://retro.hansotten.nl/uploads/6502docs/usrman.htm#F312
|
||||
// http://www.zimmers.net/anonftp/pub/cbm/documents/chipdata/6530.pdf
|
||||
// http://archive.6502.org/datasheets/mos_6530_rriot_preliminary_aug_1975.pdf
|
||||
// https://github.com/mamedev/mame/blob/master/src/mame/drivers/kim1.cpp
|
||||
// https://github.com/mamedev/mame/blob/c5426f2fabc220be2efc2790ffca57bde108cbf2/src/devices/machine/mos6530.cpp
|
||||
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
import { KIM1 } from "../machine/kim1";
|
||||
import { Base6502MachinePlatform } from "../baseplatform";
|
||||
|
||||
var KIM1_PRESETS = [
|
||||
{id:'hello.dasm', name:'Hello World (ASM)'},
|
||||
];
|
||||
|
||||
const KIM1_KEYCODE_MAP = makeKeycodeMap([
|
||||
]);
|
||||
class KIM1Platform extends Base6502MachinePlatform<KIM1> implements Platform {
|
||||
|
||||
const KIM1_KEYMATRIX_NOSHIFT = [
|
||||
Keys.VK_DELETE, Keys.VK_ENTER, Keys.VK_RIGHT, Keys.VK_F7, Keys.VK_F1, Keys.VK_F3, Keys.VK_F5, Keys.VK_DOWN,
|
||||
Keys.VK_3, Keys.VK_W, Keys.VK_A, Keys.VK_4, Keys.VK_Z, Keys.VK_S, Keys.VK_E, Keys.VK_SHIFT,
|
||||
Keys.VK_5, Keys.VK_R, Keys.VK_D, Keys.VK_6, Keys.VK_C, Keys.VK_F, Keys.VK_T, Keys.VK_X,
|
||||
Keys.VK_7, Keys.VK_Y, Keys.VK_G, Keys.VK_8, Keys.VK_B, Keys.VK_H, Keys.VK_U, Keys.VK_V,
|
||||
Keys.VK_9, Keys.VK_I, Keys.VK_J, Keys.VK_0, Keys.VK_M, Keys.VK_K, Keys.VK_O, Keys.VK_N,
|
||||
null/*Keys.VK_PLUS*/, Keys.VK_P, Keys.VK_L, Keys.VK_MINUS, Keys.VK_PERIOD, null/*Keys.VK_COLON*/, null/*Keys.VK_AT*/, Keys.VK_COMMA,
|
||||
null/*Keys.VK_POUND*/, null/*TIMES*/, Keys.VK_SEMICOLON, Keys.VK_HOME, Keys.VK_SHIFT/*right*/, Keys.VK_EQUALS, Keys.VK_TILDE, Keys.VK_SLASH,
|
||||
Keys.VK_1, Keys.VK_LEFT, Keys.VK_CONTROL, Keys.VK_2, Keys.VK_SPACE, Keys.VK_ALT, Keys.VK_Q, null/*STOP*/,
|
||||
];
|
||||
newMachine() { return new KIM1(); }
|
||||
getPresets() { return KIM1_PRESETS; }
|
||||
getDefaultExtension() { return ".dasm"; };
|
||||
readAddress(a) { return this.machine.readConst(a); }
|
||||
|
||||
const KEYBOARD_ROW_0 = 0;
|
||||
|
||||
const cpuFrequency = 1000000;
|
||||
const romLength = 0x1000;
|
||||
|
||||
class RRIOT_6530 {
|
||||
|
||||
regs = new Uint8Array(16);
|
||||
ina : number = 0;
|
||||
inb : number = 0;
|
||||
|
||||
read(a:number) : number {
|
||||
//console.log('read', hex(a), hex(this.regs[a]));
|
||||
return this.regs[a];
|
||||
}
|
||||
|
||||
write(a:number,v:number) {
|
||||
this.regs[a] = v;
|
||||
//console.log('write', hex(a), hex(v));
|
||||
}
|
||||
|
||||
input_a() { return this.ina & ~this.regs[1]; }
|
||||
input_b() { return this.inb & ~this.regs[1]; }
|
||||
output_a() { return (this.regs[0] ^ 0xff) | this.regs[1]; }
|
||||
output_b() { return (this.regs[2] ^ 0xff) | this.regs[3]; }
|
||||
}
|
||||
|
||||
class KIM1Platform extends Base6502Platform implements Platform {
|
||||
|
||||
mainElement : HTMLElement;
|
||||
cpu;
|
||||
ram : Uint8Array;
|
||||
bios : Uint8Array;
|
||||
bus;
|
||||
timer : AnimationTimer;
|
||||
inputs = new Uint8Array(16);
|
||||
rriot1 : RRIOT_6530 = new RRIOT_6530();
|
||||
rriot2 : RRIOT_6530 = new RRIOT_6530();
|
||||
digits = [];
|
||||
|
||||
constructor(mainElement : HTMLElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
}
|
||||
|
||||
getPresets() {
|
||||
return KIM1_PRESETS;
|
||||
}
|
||||
|
||||
getKeyboardMap() { return null; /* TODO: KIM1_KEYCODE_MAP;*/ }
|
||||
|
||||
getKeyboardFunction() {
|
||||
return (key,code,flags) => {
|
||||
//console.log(key,code,flags);
|
||||
var keymap = KIM1_KEYMATRIX_NOSHIFT;
|
||||
for (var i=0; i<keymap.length; i++) {
|
||||
if (keymap[i] && keymap[i].c == key) {
|
||||
let row = i >> 3;
|
||||
let col = i & 7;
|
||||
// is column selected?
|
||||
if (flags & KeyFlags.KeyDown) {
|
||||
this.inputs[KEYBOARD_ROW_0 + row] |= (1<<col);
|
||||
} else if (flags & KeyFlags.KeyUp) {
|
||||
this.inputs[KEYBOARD_ROW_0 + row] &= ~(1<<col);
|
||||
}
|
||||
console.log(key, row, col, hex(this.inputs[KEYBOARD_ROW_0 + row]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readIO_1(a:number) : number {
|
||||
return this.rriot1.read(a);
|
||||
}
|
||||
|
||||
writeIO_1(a:number, v:number) {
|
||||
this.rriot1.write(a,v);
|
||||
}
|
||||
|
||||
readIO_2(a:number) : number {
|
||||
switch (a & 0xf) {
|
||||
case 0x0:
|
||||
let cols = 0;
|
||||
for (let i=0; i<8; i++)
|
||||
if ((this.rriot2.regs[0] & (1<<i)) == 0)
|
||||
cols |= this.inputs[KEYBOARD_ROW_0 + i];
|
||||
//if (cols) console.log(this.rriot1.regs[0], cols);
|
||||
this.rriot2.ina = cols ^ 0xff;
|
||||
}
|
||||
return this.rriot2.read(a);
|
||||
}
|
||||
|
||||
writeIO_2(a:number, v:number) {
|
||||
this.rriot2.write(a,v);
|
||||
// update LED
|
||||
let digit = this.rriot2.output_a();
|
||||
let segments = this.rriot2.output_b();
|
||||
console.log(digit, segments);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.cpu = new jt.M6502();
|
||||
this.ram = new Uint8Array(0x1800);
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(KIM1_BIOS_LZG)));
|
||||
this.bus = {
|
||||
read: newAddressDecoder([
|
||||
[0x1700, 0x173f, 0x000f, (a) => { return this.readIO_1(a); }],
|
||||
[0x1740, 0x177f, 0x000f, (a) => { return this.readIO_2(a); }],
|
||||
[0x0000, 0x17ff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
[0x1800, 0x1fff, 0x07ff, (a) => { return this.bios[a]; }],
|
||||
], {gmask:0x1fff}),
|
||||
write: newAddressDecoder([
|
||||
[0x1700, 0x173f, 0x000f, (a,v) => { return this.writeIO_1(a,v); }],
|
||||
[0x1740, 0x177f, 0x000f, (a,v) => { return this.writeIO_2(a,v); }],
|
||||
[0x0000, 0x17ff, 0x1fff, (a,v) => { this.ram[a] = v; }],
|
||||
], {gmask:0x1fff}),
|
||||
};
|
||||
this.cpu.connectBus(this.bus);
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
// create digits display
|
||||
let div = $('<div class="emuvideo"/>').appendTo(this.mainElement);
|
||||
div[0].tabIndex = -1; // Make it focusable
|
||||
for (let i=0; i<6; i++) {
|
||||
let id = "kim_digit_" + i;
|
||||
let el = $('<span style="font-size:3em;font-family:monospace;color:red">0</span>').attr('id',id).appendTo(div);
|
||||
this.digits.push(el);
|
||||
}
|
||||
_setKeyboardEvents(div[0], this.getKeyboardFunction());
|
||||
}
|
||||
|
||||
advance(novideo : boolean) {
|
||||
var debugCond = this.getDebugCallback();
|
||||
for (var i=0; i<cpuFrequency/60; i++) {
|
||||
if (debugCond && debugCond()) {
|
||||
debugCond = null;
|
||||
break;
|
||||
}
|
||||
this.cpu.clockPulse();
|
||||
}
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
let rom = padBytes(data, romLength);
|
||||
this.ram.set(rom, 0x400);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadBIOS(title, data) {
|
||||
this.bios = padBytes(data, 0x800);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.timer.isRunning();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.timer.start();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
this.cpu.clockPulse(); // TODO: needed for test to pass?
|
||||
}
|
||||
|
||||
// TODO: don't log if profiler active
|
||||
readAddress(addr : number) {
|
||||
return this.bus.read(addr) | 0;
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.unfixPC(state.c);
|
||||
this.cpu.loadState(state.c);
|
||||
this.fixPC(state.c);
|
||||
this.ram.set(state.b);
|
||||
this.loadControlsState(state);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
b:this.ram.slice(0),
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.in);
|
||||
}
|
||||
|
||||
saveControlsState() {
|
||||
return {
|
||||
in:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
getCPUState() {
|
||||
return this.fixPC(this.cpu.saveState());
|
||||
}
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'RAM', start:0x0000,size:0x1400,type:'ram'},
|
||||
{name:'6530', start:0x1700,size:0x0040,type:'io'},
|
||||
{name:'6530', start:0x1740,size:0x0040,type:'io'},
|
||||
{name:'RAM', start:0x1780,size:0x0080,type:'ram'},
|
||||
{name:'BIOS', start:0x1800,size:0x0800,type:'rom'},
|
||||
] } };
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BasicZ80ScanlinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS, newAddressDecoder, padBytes, noise, makeKeycodeMap, KeyFlags, Keys, EmuHalt } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { MasterAudio, AY38910_Audio } from "../audio";
|
||||
import { TMS9918A } from "../video/tms9918a";
|
||||
import { MSX1 } from "../machine/msx";
|
||||
import { Platform, BaseZ80MachinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
// https://www.konamiman.com/msx/msx-e.html#msx2th
|
||||
// https://www.msx.org/wiki/MSX_Cartridge_slot
|
||||
|
@ -39,439 +36,26 @@ var LIBCV_PRESETS = [
|
|||
{ id: 'climber.c', name: 'Platform Game' },
|
||||
];
|
||||
|
||||
var MSX_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x2],
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.A, 0, 0x10],
|
||||
[Keys.B, 0, 0x20],
|
||||
class MSXPlatform extends BaseZ80MachinePlatform<MSX1> implements Platform {
|
||||
|
||||
[Keys.P2_UP, 1, 0x1],
|
||||
[Keys.P2_DOWN, 1, 0x2],
|
||||
[Keys.P2_LEFT, 1, 0x4],
|
||||
[Keys.P2_RIGHT, 1, 0x8],
|
||||
[Keys.P2_A, 1, 0x10],
|
||||
[Keys.P2_B, 1, 0x20],
|
||||
|
||||
[Keys.ANYKEY, 2, 0x0],
|
||||
]);
|
||||
|
||||
const JOY_INPUT_0 = 0;
|
||||
const JOY_INPUT_1 = 1;
|
||||
const KEYBOARD_ROW_0 = 16;
|
||||
|
||||
const MSX_KEYMATRIX_INTL_NOSHIFT = [
|
||||
Keys.VK_7, Keys.VK_6, Keys.VK_5, Keys.VK_4, Keys.VK_3, Keys.VK_2, Keys.VK_1, Keys.VK_0,
|
||||
Keys.VK_SEMICOLON, Keys.VK_CLOSE_BRACKET, Keys.VK_OPEN_BRACKET, Keys.VK_BACK_SLASH, Keys.VK_EQUALS, Keys.VK_MINUS, Keys.VK_9, Keys.VK_8,
|
||||
Keys.VK_B, Keys.VK_A, null/*DEAD*/, Keys.VK_SLASH, Keys.VK_PERIOD, Keys.VK_COMMA, Keys.VK_ACUTE, Keys.VK_QUOTE,
|
||||
Keys.VK_J, Keys.VK_I, Keys.VK_H, Keys.VK_G, Keys.VK_F, Keys.VK_E, Keys.VK_D, Keys.VK_C,
|
||||
Keys.VK_R, Keys.VK_Q, Keys.VK_P, Keys.VK_O, Keys.VK_N, Keys.VK_M, Keys.VK_L, Keys.VK_K,
|
||||
Keys.VK_Z, Keys.VK_Y, Keys.VK_X, Keys.VK_W, Keys.VK_V, Keys.VK_U, Keys.VK_T, Keys.VK_S,
|
||||
Keys.VK_F3, Keys.VK_F2, Keys.VK_F1, null, Keys.VK_CAPS_LOCK, null, Keys.VK_CONTROL, Keys.VK_SHIFT,
|
||||
Keys.VK_ENTER, null, Keys.VK_BACK_SPACE, null, Keys.VK_TAB, Keys.VK_ESCAPE, Keys.VK_F5, Keys.VK_F4,
|
||||
Keys.VK_RIGHT, Keys.VK_DOWN, Keys.VK_UP, Keys.VK_LEFT, Keys.VK_DELETE, Keys.VK_INSERT, Keys.VK_HOME, Keys.VK_SPACE,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
// TODO: null keycodes
|
||||
];
|
||||
|
||||
/// standard emulator
|
||||
|
||||
interface MSXSlot {
|
||||
read(addr:number) : number;
|
||||
write(addr:number, val:number) : void;
|
||||
newMachine() { return new MSX1(); }
|
||||
getPresets() { return MSX_BIOS_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
readVRAMAddress(a) { return this.machine.readVRAMAddress(a); }
|
||||
// TODO loadBios(bios) { this.machine.loadBIOS(a); }
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'BIOS',start:0x0,size:0x4000,type:'rom'},
|
||||
//{name:'Cartridge',start:0x4000,size:0x4000,type:'rom'},
|
||||
{name:'RAM',start:0xc000,size:0x3200,type:'ram'},
|
||||
{name:'Stack',start:0xf000,size:0x300,type:'ram'},
|
||||
{name:'BIOS Work RAM',start:0xf300,size:0xd00},
|
||||
] } };
|
||||
}
|
||||
|
||||
class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform {
|
||||
|
||||
cpuFrequency = 3579545; // MHz
|
||||
canvasWidth = 304;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 240;
|
||||
defaultROMSize = 0x8000;
|
||||
|
||||
vdp : TMS9918A;
|
||||
bios : Uint8Array;
|
||||
slots : MSXSlot[];
|
||||
slotmask : number = 0;
|
||||
ppi_c : number = 0;
|
||||
|
||||
getPresets() { return MSX_BIOS_PRESETS; }
|
||||
|
||||
getKeyboardMap() { return MSX_KEYCODE_MAP; }
|
||||
|
||||
// http://map.grauw.nl/articles/keymatrix.php
|
||||
getKeyboardFunction() {
|
||||
return (o,key,code,flags) => {
|
||||
//console.log(o,key,code,flags);
|
||||
var keymap = MSX_KEYMATRIX_INTL_NOSHIFT;
|
||||
for (var i=0; i<keymap.length; i++) {
|
||||
if (keymap[i] && keymap[i].c == key) {
|
||||
let row = i >> 3;
|
||||
let bit = 7 - (i & 7);
|
||||
//console.log(key, row, bit);
|
||||
if (flags & KeyFlags.KeyDown) {
|
||||
this.inputs[KEYBOARD_ROW_0+row] |= (1<<bit);
|
||||
} else if (flags & KeyFlags.KeyUp) {
|
||||
this.inputs[KEYBOARD_ROW_0+row] &= ~(1<<bit);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVideoOptions() { return {overscan:true}; }
|
||||
|
||||
newRAM() {
|
||||
return new Uint8Array(0x10000);
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
// slot mapper
|
||||
return {
|
||||
read: (a) => {
|
||||
let shift = (a >> 14) << 1;
|
||||
let slotnum = (this.slotmask >> shift) & 3;
|
||||
let slot = this.slots[slotnum];
|
||||
return slot ? slot.read(a) : 0;
|
||||
},
|
||||
write: (a,v) => {
|
||||
let shift = (a >> 14) << 1;
|
||||
let slotnum = (this.slotmask >> shift) & 3;
|
||||
let slot = this.slots[slotnum];
|
||||
if (slot) slot.write(a, v);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0x98: return this.vdp.readData();
|
||||
case 0x99: return this.vdp.readStatus();
|
||||
case 0xa2:
|
||||
if (this.psg.currentRegister() == 14) return ~this.inputs[JOY_INPUT_0]; // TODO: joy 1?
|
||||
else return this.psg.readData();
|
||||
case 0xa8: return this.slotmask;
|
||||
case 0xa9: return ~this.inputs[KEYBOARD_ROW_0 + (this.ppi_c & 15)];
|
||||
case 0xaa: return this.ppi_c; // TODO?
|
||||
//default: throw new EmuHalt("Read I/O " + hex(addr));
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr, val) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr) {
|
||||
case 0x98: this.vdp.writeData(val); break;
|
||||
case 0x99: this.vdp.writeAddress(val); break;
|
||||
case 0xa8: this.slotmask = val; break;
|
||||
case 0xaa: this.ppi_c = val; break;
|
||||
case 0xab: // command register, modifies PPI C
|
||||
let ibit = (val >> 1) & 7;
|
||||
this.ppi_c = (this.ppi_c & ~(1<<ibit)) | ((val&1)<<ibit);
|
||||
break;
|
||||
case 0xa0: this.psg.selectRegister(val); break;
|
||||
case 0xa1: this.psg.setData(val); break;
|
||||
case 0xfc:
|
||||
case 0xfd:
|
||||
case 0xfe:
|
||||
case 0xff:
|
||||
break; // memory mapper (MSX2)
|
||||
//default: throw new EmuHalt("Write I/O " + hex(addr));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.bios = new lzgmini().decode(stringToByteArray(atob(MSX1_BIOS_LZG)));
|
||||
// skip splash screen
|
||||
this.bios[0xdd5] = 0;
|
||||
this.bios[0xdd6] = 0;
|
||||
this.bios[0xdd7] = 0;
|
||||
// slot definitions
|
||||
this.slots = [
|
||||
// slot 0 : BIOS
|
||||
{
|
||||
read: (a) => { return this.bios[a] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 1: cartridge
|
||||
{
|
||||
read: (a) => { return this.rom[a - 0x4000] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 2: cartridge
|
||||
{
|
||||
read: (a) => { return this.rom[a - 0x4000] | 0; },
|
||||
write: (a,v) => { }
|
||||
},
|
||||
// slot 3 : RAM
|
||||
{
|
||||
read: (a) => { return this.ram[a] | 0; },
|
||||
write: (a,v) => { this.ram[a] = v; }
|
||||
},
|
||||
];
|
||||
this.audio = new MasterAudio();
|
||||
this.psg = new AY38910_Audio(this.audio);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
this.cpu.requestInterrupt(0x38);
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
}
|
||||
};
|
||||
this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new TMS9918A(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
startScanline(sl : number) {
|
||||
}
|
||||
|
||||
drawScanline(sl : number) {
|
||||
this.vdp.drawScanline(sl);
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.vdp.restoreState(state['vdp']);
|
||||
this.slotmask = state['slotmask'];
|
||||
this.ppi_c = state['ppi_c'];
|
||||
this.psg.selectRegister(state['psgRegister']);
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['vdp'] = this.vdp.getState();
|
||||
state['slotmask'] = this.slotmask;
|
||||
state['ppi_c'] = this.ppi_c;
|
||||
state['psgRegister'] = this.psg.currentRegister();
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.vdp.reset();
|
||||
this.psg.reset();
|
||||
this.slotmask = 0;
|
||||
this.ppi_c = 0;
|
||||
}
|
||||
resume() {
|
||||
super.resume();
|
||||
this.resetInputs();
|
||||
}
|
||||
resetInputs() {
|
||||
// clear keyboard matrix
|
||||
this.inputs.fill(0);
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return super.getDebugCategories().concat(['VDP']);
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
vdpStateToLongString(ppu) {
|
||||
return this.vdp.getRegsString();
|
||||
}
|
||||
readVRAMAddress(a : number) : number {
|
||||
return this.vdp.ram[a & 0x3fff];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
class MSXLibCVPlatform extends MSXPlatform implements Platform {
|
||||
getPresets() { return LIBCV_PRESETS; }
|
||||
}
|
||||
|
||||
PLATFORMS['msx'] = MSXPlatform;
|
||||
PLATFORMS['msx-libcv'] = MSXLibCVPlatform;
|
||||
|
||||
///
|
||||
|
||||
/*
|
||||
C-BIOS is a BIOS compatible with the MSX BIOS
|
||||
C-BIOS was written from scratch by BouKiCHi
|
||||
C-BIOS is available for free, including its source code (2-clause BSD license)
|
||||
C-BIOS can be shipped with MSX emulators so they are usable out-of-the-box without copyright issues
|
||||
|
||||
http://cbios.sourceforge.net/
|
||||
*/
|
||||
var MSX1_BIOS_LZG = `
|
||||
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
|
||||
KERwgHCIO4RKkwL///CTAQ+TAVFEkwQ7yBM8UZpRzMCTBVFkUWz8kwUDkwU/kwURIkSIk2KIRCIR
|
||||
k2L+fDg7Z2IQOHz+gMDg8ODAgAABAwcPBwMBAP9+PBgYPH7/gcPn///nw4E7AlQ7Big7AmQ7BJST
|
||||
BFFUMzPMzJNiABAoKHw7o+g4kyE7AltQiKhQOwy+k+JRbDsG3JMCOwjaaJA7gnBgkOCQkOCAAPiI
|
||||
O4KpAAD4UJMBSFEISCBAiDtDmHiQOyQmiIjIsFFaUFAgk0FwIHCoqJOBOyK+O6WAiFDYO6KgMEg7
|
||||
ZLBQqDsCiAAQUWBAURmA4IA7BSA7gwA7oo87Alg7wyhRATujmJPhO6Owk+I7ZbqTA8BRIFEmOyLI
|
||||
aLCTQjuCiTsEXzB4eJPjUUOTYRwQEJBQMBAA4JCQUYxgEGA7JEhwkwMAqlWTJMXl9Ven8/zeJPHh
|
||||
5dXl9VoDDN5vRz78zfgjX0Xx5gOT4UfbqFejsOHNgPN70fXLesQaJfHhwckEBcgHBxD8yeVX1VG4
|
||||
weHV5TsMNno7CjZZzYXz0eVRtuHJCNnz/eXx3eXhV6dRMtVRIUcO/N3l8VF3hygHywDLAT0g+SFn
|
||||
JOXbqPWhsNnDjPNRK9FRtgjZyfPlb1EqPqvGVQXyeyRXOwNuZ0c+wAcHBfKLJF8vT3qjR32n8tYk
|
||||
Dw/mA+XFOwUipSSjR3rmwGfbqG/mwLRaBAxiobBPMv//fdOoIcX8euYDhW98zgBneXfB4duoobDT
|
||||
qOHJVw8PX+bAb9uoT+Y/tdOoe+YDb3wmAxgCKSnWQDD6fC9nUX5fpLVRPm9506hRPE8GAH0hxfwJ
|
||||
d8l6Dw/mwEdRs7BRM1EbOwcaUVlzydOoXhgD06hzetOoUQQIzZjzCPGT4cnd6QBaBQ87MC4yOTsk
|
||||
8mNiaW9zLnNmLm5ldA0KQ2hhcmFjdGVyIHNldDogVVMNCkluUQdydXB0IGZyZXF1ZW5jeTogNjBI
|
||||
eg0KS2V5Ym9hcmQgdHlwZVHmkyQASW5pdCBST00gaW4gc2xvdDogAENhbm5vdCBleGVjdXRlIGEg
|
||||
QkFTSUNRXS5RKUVSUk9SOgBNRU1PUlkgTk9UIEZPVU5ELgBDQUxMRURRCk4gRVhJU1RJTkdR8i4A
|
||||
U1RBQ0sgUbIuADsCb05vIGNhcnRyaWRnZSBmb3VuZC5RT1RoaXMgdmVyc2lvbiBvZiA7BfxjYW4N
|
||||
Cm9ubHkgc3RRLjsIM3NRLlBsZWFzZSByZVHUeW91ciBNU1gNCihlbXVsYXRvcikgd2l0aCBhOwgw
|
||||
DQppbnNlcnRlZC4AMDEyMzQ1Njc4OS09XFtdOwAnYCwuLwBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2
|
||||
d3h5eikhQCMkJV4mKihfK3x7fToifjw+PwBBQkNERUZHSElKS0xNTk9QUQBSU1RVVldYWVoACayr
|
||||
uu+99PvsBxfxHgENBgW78/IdAMQRvMfNFBUT3MbdyAsbwtvMGNISwBrPHBkPCgD9/AAA9QAACB/w
|
||||
FgIOBAP3rq/2AP4A+sHO1BDW38reyQzTw9fLqdEAxdXQ+ar465/Zv5uY4OHnh+7pAO3at7nlhqan
|
||||
AISXjYuMlIGxoZGzteakoqODkwCJloKViIqghditnr6cnQAA4jvCpOjqtrjkjwCoAI5RBQCZmrAA
|
||||
krK0AKUA41FHO4QvkwgbCQAIAA0gDAAAHR4fHCorLzspMywuAIBwgQCCAYT1h1oEAnWTH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+THpMdkwPF5cEbe7IoAwkY+FoDGKu1Mc2fCT4g
|
||||
0y560y970y/x4clST01CQVM7H6CTH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5MekxyTBj46kz+T
|
||||
P5Ms5fUhSTpaBRvgWgMMYHN0YXRlbWVudHMgYXJlIG5vdCBpbXBsUYtlZCB5ZXQ7P7uTH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TBc2O
|
||||
WgpBt5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5MfWh5ixDsfeZMfkx+TH5Mfkx+TH5Mfkx+TH5MeAOX1ISJ9WgQ60XVua25v
|
||||
d25AN0QxNwA5fd0hiQHNPxrJ3SGFAcM/Wh9abpMeOw71H347DfVFMTQ735aTH5Mfkx6TC1oNaeX/
|
||||
zW8AWgdqSmIAKiL5AQADPgDNVgABAALNRwABA5+TogQAk6FaA3T+B5MBR1oDcC9aA3AqyfNRIyok
|
||||
US4ECesBMAMhvIDNXFHJAQnrKgQACQEAA1FKyfM7Bxvsg1HJEQABGVEWPvFReyL5AYQACeshHIcG
|
||||
CsXl1QEYAFEd4QEgUQzhUQUJwRDpO01I/viTYh9/k6E/OwwSHzsIAvj+OwYDfx9aA1VJ/vz48B8/
|
||||
US2TBVFIAFG+kwk7CS8Afz8f+Pw7CwdaBVbh/v4fP387CUH+/DsGUPiTAfCTAR+TAT+TAVFIUdBR
|
||||
zTsGKzsGCDsHJMffUQ3wOwi3WgZcfjsDen8//pMB/Pz8fDsGKAAA/PgfP3/+UWnwf1Ef/FFk8FFr
|
||||
fz8fH/D++PAff3z8+D8/f5MDOwZMOwYIOwaYUUdRUFEzkwN8PDw8x5MBHx7j45Pi/Pjw8FFhOwb/
|
||||
OwUfkwT+fzstdn87B7hRKz9+kwFRHvAfHz9+OwJEOwK0k+J4eHh8UVg/OyRI/vz8+DsDTzsD0loI
|
||||
XXOTBTsEcfD4/HyTAlEDf39/PzsKcFGIOwJkOwwv/B+TgfA7Itw8jx9/OwJ0n5MBfH5+f+M+Pz87
|
||||
JE7wPlEiOwZEOyI6OyLQ/vjxPPA7Isc7JS1RkHz8+DsiaAA/OyJV8Pj8k8L4UT07TRc7TTU7BwY7
|
||||
BFz++D87S3k7SYo7CEB/OwovOwYCOyJQ/viTofA7Tac7AvaTCztz8gkJk2KQkAmTgZNhkxFRGFGX
|
||||
OwUnkAGTARmTAZGRkTsHHFEEkQmRGRlRVJMGOwYIOwcZOwUoOwU4kTsGeDsHETsFIFEUUbg7QoCT
|
||||
CvGTAVFQCFEfCJCQkJPjk2GBUSyBUS07BhyTgQ8Pk8EPkwFRaJPiH1FBUVZRUTsJSJPiURFRhFEu
|
||||
D/FRRlHBUWtRnzsFI/GAkJCAkICAOwJwCAkICAkYGRkYGRgYGVGVOwdEAQE7B1k7B1w7Bho7BnQ7
|
||||
JBg7CA6TBFHOOwSsOwJzCAg7Al8ICfHx4ZNB4VE0Hh+T4lFIHx5RBJPlUVCT4VF4k+JRkPHhk+E7
|
||||
BWA7BkoPDx47BXA7BEgICAhaA1/Qk+GTA1ETGJMC4ZMBDpMBUWST4x4eUQhRTuHhHpNikwFRhDsG
|
||||
AVEnUUZRJx47BTCT4lHSUZ4BAVE5OwsQUU07BnQYGBiBkwIICDtEClELUQZRDTsHkFGak+GTAxiT
|
||||
pBiBUW47BrRRq1EsUVU7CEA7AliT4pMCOwffUYWAURCEOwUhBDtmAwSTDVHOAICAgYKDhIWGhzsj
|
||||
JpMJiImKi4yNjo+QjJMK45GSkwCUjJWWUUSXOwkQ5JiZmoyMm5ydnp+goaKjnaSlpp2TAYzkp6ip
|
||||
jIyqq4yMrK2ur4yMsLGys7OztIzktbVRLraTYbe4ubqMjLu8vb6+v8CM5MHCw4yMxMXFxsfIycrL
|
||||
zM3OxcXFz9CM5NHC0jsLgZMF5NPU1dbX2Nna2zsHC1YwLjI55IDc3d7C3+Dh4pMM5VoGXW2TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mf
|
||||
kx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+T
|
||||
H5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+TH5Mfkx+THpMc/w==
|
||||
`;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BasicZ80ScanlinePlatform, BaseZ80Platform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex } from "../util";
|
||||
import { Midway8080 } from "../machine/mw8080bw";
|
||||
import { BaseZ80MachinePlatform } from "../baseplatform";
|
||||
import { Platform } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
// http://www.computerarcheology.com/Arcade/
|
||||
|
||||
|
@ -12,142 +13,17 @@ const MW8080BW_PRESETS = [
|
|||
{ id: 'game2.c', name: 'Cosmic Impalas' },
|
||||
];
|
||||
|
||||
const SPACEINV_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 1, 0x10], // P1
|
||||
[Keys.LEFT, 1, 0x20],
|
||||
[Keys.RIGHT, 1, 0x40],
|
||||
[Keys.P2_A, 2, 0x10], // P2
|
||||
[Keys.P2_LEFT, 2, 0x20],
|
||||
[Keys.P2_RIGHT, 2, 0x40],
|
||||
[Keys.SELECT, 1, 0x1],
|
||||
[Keys.START, 1, 0x4],
|
||||
[Keys.P2_START, 1, 0x2],
|
||||
]);
|
||||
class Midway8080BWPlatform extends BaseZ80MachinePlatform<Midway8080> implements Platform {
|
||||
|
||||
const INITIAL_WATCHDOG = 256;
|
||||
const PIXEL_ON = 0xffeeeeee;
|
||||
const PIXEL_OFF = 0xff000000;
|
||||
newMachine() { return new Midway8080(); }
|
||||
getPresets() { return MW8080BW_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'Frame Buffer',start:0x2400,size:7168,type:'ram'},
|
||||
] } };
|
||||
|
||||
|
||||
class Midway8080BWPlatform extends BasicZ80ScanlinePlatform implements Platform {
|
||||
cpuFrequency = 1996800; // MHz
|
||||
canvasWidth = 256;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 224;
|
||||
defaultROMSize = 0x2000;
|
||||
|
||||
bitshift_offset = 0;
|
||||
bitshift_register = 0;
|
||||
watchdog_counter;
|
||||
|
||||
getPresets() { return MW8080BW_PRESETS; }
|
||||
getKeyboardMap() { return SPACEINV_KEYCODE_MAP; }
|
||||
getVideoOptions() { return { rotate: -90 }; }
|
||||
newRAM() { return new Uint8Array(0x2000); }
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x1fff, 0x1fff, (a) => { return this.rom ? this.rom[a] : 0; }],
|
||||
[0x2000, 0x3fff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x2000, 0x23ff, 0x3ff, (a, v) => { this.ram[a] = v; }],
|
||||
[0x2400, 0x3fff, 0x1fff, (a, v) => {
|
||||
this.ram[a] = v;
|
||||
var ofs = (a - 0x400) << 3;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
this.pixels[ofs + i] = (v & (1 << i)) ? PIXEL_ON : PIXEL_OFF;
|
||||
}
|
||||
//if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter
|
||||
}],
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
addr &= 0x3;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return this.inputs[addr];
|
||||
case 3:
|
||||
return (this.bitshift_register >> (8 - this.bitshift_offset)) & 0xff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr, val) => {
|
||||
addr &= 0x7;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr) {
|
||||
case 2:
|
||||
this.bitshift_offset = val & 0x7;
|
||||
break;
|
||||
case 3:
|
||||
case 5:
|
||||
// TODO: sound
|
||||
break;
|
||||
case 4:
|
||||
this.bitshift_register = (this.bitshift_register >> 8) | (val << 8);
|
||||
break;
|
||||
case 6:
|
||||
this.watchdog_counter = INITIAL_WATCHDOG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
startScanline(sl: number) {
|
||||
}
|
||||
|
||||
drawScanline(sl: number) {
|
||||
// at end of scanline
|
||||
if (sl == 95)
|
||||
this.cpu.requestInterrupt(0x8); // RST $8
|
||||
else if (sl == 223)
|
||||
this.cpu.requestInterrupt(0x10); // RST $10
|
||||
}
|
||||
|
||||
advance(novideo: boolean) {
|
||||
super.advance(novideo);
|
||||
if (this.watchdog_counter-- <= 0) {
|
||||
console.log("WATCHDOG FIRED"); // TODO: alert on video
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.bitshift_register = state.bsr;
|
||||
this.bitshift_offset = state.bso;
|
||||
this.watchdog_counter = state.wdc;
|
||||
}
|
||||
saveState() {
|
||||
var state: any = super.saveState();
|
||||
state.bsr = this.bitshift_register;
|
||||
state.bso = this.bitshift_offset;
|
||||
state.wdc = this.watchdog_counter;
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.watchdog_counter = INITIAL_WATCHDOG;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
$(video.canvas).click(function(e) {
|
||||
var x = Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width());
|
||||
var y = Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height());
|
||||
var addr = (x>>3) + (y*32) + 0x400;
|
||||
if (displayPCs) console.log(x, y, hex(addr,4), "PC", hex(displayPCs[addr],4));
|
||||
});
|
||||
*/
|
||||
|
||||
PLATFORMS['mw8080bw'] = Midway8080BWPlatform;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString, ProfilerOutput } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt, ControllerPoller } from "../emu";
|
||||
import { hex, lpad, lzgmini, byteArrayToString } from "../util";
|
||||
import { CodeAnalyzer_nes } from "../analysis";
|
||||
import { SampleAudio } from "../audio";
|
||||
import { ProbeRecorder } from "../recorder";
|
||||
import { NullProbe, Probeable, ProbeAll } from "../devices";
|
||||
|
||||
declare var jsnes : any;
|
||||
declare var Mousetrap;
|
||||
|
@ -65,7 +66,7 @@ const JSNES_KEYCODE_MAP = makeKeycodeMap([
|
|||
[Keys.P2_RIGHT, 1, 7],
|
||||
]);
|
||||
|
||||
class JSNESPlatform extends Base6502Platform implements Platform {
|
||||
class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
|
||||
|
||||
mainElement;
|
||||
nes;
|
||||
|
@ -78,6 +79,8 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
ntvideo;
|
||||
ntlastbuf;
|
||||
|
||||
machine = { cpuCyclesPerLine: 114 }; // TODO: hack for width of probe scope
|
||||
|
||||
constructor(mainElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
|
@ -135,8 +138,10 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
// insert debug hook
|
||||
this.nes.cpu._emulate = this.nes.cpu.emulate;
|
||||
this.nes.cpu.emulate = () => {
|
||||
this.probe.logExecute(this.nes.cpu.REG_PC-1);
|
||||
var cycles = this.nes.cpu._emulate();
|
||||
this.evalDebugCondition();
|
||||
this.probe.logClocks(cycles);
|
||||
return cycles;
|
||||
}
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
|
@ -199,6 +204,59 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
var romstr = byteArrayToString(data);
|
||||
this.nes.loadROM(romstr);
|
||||
this.frameindex = 0;
|
||||
this.installIntercepts();
|
||||
}
|
||||
installIntercepts() {
|
||||
// intercept bus calls, unless we did it already
|
||||
var mmap = this.nes.mmap;
|
||||
if (!mmap.haveProxied) {
|
||||
var oldload = mmap.load.bind(mmap);
|
||||
var oldwrite = mmap.write.bind(mmap);
|
||||
var oldregLoad = mmap.regLoad.bind(mmap);
|
||||
var oldregWrite = mmap.regWrite.bind(mmap);
|
||||
var lastioaddr = -1;
|
||||
mmap.load = (addr) => {
|
||||
var val = oldload(addr);
|
||||
if (addr != lastioaddr) this.probe.logRead(addr, val);
|
||||
return val;
|
||||
}
|
||||
mmap.write = (addr, val) => {
|
||||
if (addr != lastioaddr) this.probe.logWrite(addr, val);
|
||||
oldwrite(addr, val);
|
||||
}
|
||||
// try not to read/write then IOread/IOwrite at same time
|
||||
mmap.regLoad = (addr) => {
|
||||
var val = oldregLoad(addr);
|
||||
this.probe.logIORead(addr, val);
|
||||
lastioaddr = addr;
|
||||
return val;
|
||||
}
|
||||
mmap.regWrite = (addr, val) => {
|
||||
this.probe.logIOWrite(addr, val);
|
||||
lastioaddr = addr;
|
||||
oldregWrite(addr, val);
|
||||
}
|
||||
mmap.haveProxied = true;
|
||||
}
|
||||
var ppu = this.nes.ppu;
|
||||
if (!ppu.haveProxied) {
|
||||
var old_endScanline = ppu.endScanline.bind(ppu);
|
||||
var old_startFrame = ppu.startFrame.bind(ppu);
|
||||
var old_writeMem = ppu.writeMem.bind(ppu);
|
||||
ppu.endScanline = () => {
|
||||
old_endScanline();
|
||||
this.probe.logNewScanline();
|
||||
}
|
||||
ppu.startFrame = () => {
|
||||
old_startFrame();
|
||||
this.probe.logNewFrame();
|
||||
}
|
||||
ppu.writeMem = (a,v) => {
|
||||
old_writeMem(a,v);
|
||||
this.probe.logVRAMWrite(a,v);
|
||||
}
|
||||
ppu.haveProxied = true;
|
||||
}
|
||||
}
|
||||
newCodeAnalyzer() {
|
||||
return new CodeAnalyzer_nes(this);
|
||||
|
@ -211,6 +269,7 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
reset() {
|
||||
//this.nes.cpu.reset(); // doesn't work right, crashes
|
||||
this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
|
||||
this.installIntercepts();
|
||||
}
|
||||
isRunning() {
|
||||
return this.timer.isRunning();
|
||||
|
@ -232,9 +291,6 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
getRasterScanline() : number {
|
||||
return this.nes.ppu.scanline;
|
||||
}
|
||||
readVRAMAddress(addr : number) : number {
|
||||
return this.nes.ppu.vramMem[addr & 0x7fff];
|
||||
}
|
||||
|
||||
getCPUState() {
|
||||
var c = this.nes.cpu.toJSON();
|
||||
|
@ -265,6 +321,7 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
this.nes.ppu.spriteMem = state.ppu.spriteMem.slice(0);
|
||||
this.loadControlsState(state.ctrl);
|
||||
//$.extend(this.nes, state);
|
||||
this.installIntercepts();
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
|
@ -277,7 +334,10 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
this.nes.controllers[2].state = state.c2;
|
||||
}
|
||||
readAddress(addr) {
|
||||
return this.nes.cpu.mem[addr] & 0xff;
|
||||
return this.nes.cpu.mem[addr];
|
||||
}
|
||||
readVRAMAddress(addr : number) : number {
|
||||
return this.nes.ppu.vramMem[addr];
|
||||
}
|
||||
copy6502REGvars(c) {
|
||||
c.T = 0;
|
||||
|
@ -388,6 +448,30 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
|||
if (fn.endsWith(".nesasm")) return "nesasm";
|
||||
else return getToolForFilename_6502(fn);
|
||||
}
|
||||
|
||||
// probing
|
||||
nullProbe = new NullProbe();
|
||||
probe : ProbeAll = this.nullProbe;
|
||||
|
||||
startProbing?() : ProbeRecorder {
|
||||
var rec = new ProbeRecorder(this);
|
||||
this.connectProbe(rec);
|
||||
return rec;
|
||||
}
|
||||
stopProbing?() : void {
|
||||
this.connectProbe(null);
|
||||
}
|
||||
connectProbe(probe:ProbeAll) {
|
||||
this.probe = probe || this.nullProbe;
|
||||
}
|
||||
|
||||
getMemoryMap = function() { return { main:[
|
||||
//{name:'Work RAM',start:0x0,size:0x800,type:'ram'},
|
||||
{name:'OAM Buffer',start:0x200,size:0x100,type:'ram'},
|
||||
{name:'PPU Registers',start:0x2000,last:0x2008,size:0x2000,type:'io'},
|
||||
{name:'APU Registers',start:0x4000,last:0x4020,size:0x2000,type:'io'},
|
||||
{name:'Cartridge RAM',start:0x6000,size:0x2000,type:'ram'},
|
||||
] } };
|
||||
}
|
||||
|
||||
/// MAME support
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BasicZ80ScanlinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex, lzgmini, stringToByteArray } from "../util";
|
||||
import { MasterAudio, SN76489_Audio } from "../audio";
|
||||
import { TMS9918A, SMSVDP } from "../video/tms9918a";
|
||||
import { ColecoVision_PRESETS } from "./coleco";
|
||||
|
||||
// http://www.smspower.org/Development/Index
|
||||
// http://www.smspower.org/uploads/Development/sg1000.txt
|
||||
// http://www.smspower.org/uploads/Development/richard.txt
|
||||
// http://www.smspower.org/uploads/Development/msvdp-20021112.txt
|
||||
// http://www.smspower.org/uploads/Development/SN76489-20030421.txt
|
||||
import { SG1000, SMS } from "../machine/sms";
|
||||
import { Platform, BaseZ80MachinePlatform, BaseMAMEPlatform, getToolForFilename_z80 } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
// TODO: merge w/ coleco
|
||||
export var SG1000_PRESETS = [
|
||||
|
@ -36,282 +26,24 @@ export var SMS_PRESETS = [
|
|||
{id:'climber.c', name:'Climber Game'},
|
||||
];
|
||||
|
||||
var SG1000_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.UP, 0, 0x1],
|
||||
[Keys.DOWN, 0, 0x2],
|
||||
[Keys.LEFT, 0, 0x4],
|
||||
[Keys.RIGHT, 0, 0x8],
|
||||
[Keys.A, 0, 0x10],
|
||||
[Keys.B, 0, 0x20],
|
||||
|
||||
[Keys.P2_UP, 0, 0x40],
|
||||
[Keys.P2_DOWN, 0, 0x80],
|
||||
[Keys.P2_LEFT, 1, 0x1],
|
||||
[Keys.P2_RIGHT, 1, 0x2],
|
||||
[Keys.P2_A, 1, 0x4],
|
||||
[Keys.P2_B, 1, 0x8],
|
||||
[Keys.VK_BACK_SLASH, 1, 0x10], // reset
|
||||
]);
|
||||
|
||||
class SG1000Platform extends BasicZ80ScanlinePlatform implements Platform {
|
||||
|
||||
cpuFrequency = 3579545; // MHz
|
||||
canvasWidth = 304;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 240;
|
||||
defaultROMSize = 0xc000;
|
||||
|
||||
vdp : TMS9918A;
|
||||
|
||||
getPresets() { return SG1000_PRESETS; }
|
||||
|
||||
getKeyboardMap() { return SG1000_KEYCODE_MAP; }
|
||||
|
||||
getVideoOptions() { return {overscan:true}; }
|
||||
|
||||
newRAM() {
|
||||
return new Uint8Array(0x400);
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x3ff, (a) => { return this.ram[a]; }],
|
||||
[0x0000, 0xbfff, 0xffff, (a) => { return this.rom[a]; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x3ff, (a,v) => { this.ram[a] = v; }],
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
getVCounter() : number { return 0; }
|
||||
getHCounter() : number { return 0; }
|
||||
setMemoryControl(v:number) { }
|
||||
setIOPortControl(v:number) { }
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr:number) => {
|
||||
addr &= 0xff;
|
||||
//console.log('IO read', hex(addr,4));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x40: return this.getVCounter();
|
||||
case 0x41: return this.getHCounter();
|
||||
case 0x80: return this.vdp.readData();
|
||||
case 0x81: return this.vdp.readStatus();
|
||||
case 0xc0: return this.inputs[0] ^ 0xff;
|
||||
case 0xc1: return this.inputs[1] ^ 0xff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
write: (addr:number, val:number) => {
|
||||
addr &= 0xff;
|
||||
val &= 0xff;
|
||||
//console.log('IO write', hex(addr,4), hex(val,2));
|
||||
switch (addr & 0xc1) {
|
||||
case 0x00: return this.setMemoryControl(val);
|
||||
case 0x01: return this.setIOPortControl(val);
|
||||
case 0x40:
|
||||
case 0x41: return this.psg.setData(val);
|
||||
case 0x80: return this.vdp.writeData(val);
|
||||
case 0x81: return this.vdp.writeAddress(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new TMS9918A(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.audio = new MasterAudio();
|
||||
this.psg = new SN76489_Audio(this.audio);
|
||||
var cru = {
|
||||
setVDPInterrupt: (b) => {
|
||||
if (b) {
|
||||
this.cpu.nonMaskableInterrupt();
|
||||
} else {
|
||||
// TODO: reset interrupt?
|
||||
}
|
||||
}
|
||||
};
|
||||
this.vdp = this.newVDP(this.video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
}
|
||||
|
||||
startScanline(sl : number) {
|
||||
}
|
||||
|
||||
drawScanline(sl : number) {
|
||||
this.vdp.drawScanline(sl);
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.vdp.restoreState(state['vdp']);
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['vdp'] = this.vdp.getState();
|
||||
return state;
|
||||
}
|
||||
reset() {
|
||||
super.reset();
|
||||
this.vdp.reset();
|
||||
this.psg.reset();
|
||||
}
|
||||
|
||||
getDebugCategories() {
|
||||
return super.getDebugCategories().concat(['VDP']);
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'VDP': return this.vdpStateToLongString(state.vdp);
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
vdpStateToLongString(ppu) {
|
||||
return this.vdp.getRegsString();
|
||||
}
|
||||
readVRAMAddress(a : number) : number {
|
||||
return this.vdp.ram[a & 0x3fff];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
class SMSPlatform extends SG1000Platform {
|
||||
class SG1000Platform extends BaseZ80MachinePlatform<SG1000> implements Platform {
|
||||
|
||||
cartram = new Uint8Array(0);
|
||||
pagingRegisters = new Uint8Array(4);
|
||||
romPageMask : number;
|
||||
latchedHCounter = 0;
|
||||
ioControlFlags = 0;
|
||||
// TODO: hide bottom scanlines
|
||||
|
||||
getPresets() { return SMS_PRESETS; }
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.pagingRegisters.set([0,0,1,2]);
|
||||
}
|
||||
newMachine() { return new SG1000(); }
|
||||
getPresets() { return SG1000_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
readVRAMAddress(a) { return this.machine.readVRAMAddress(a); }
|
||||
}
|
||||
|
||||
newVDP(frameData, cru, flicker) {
|
||||
return new SMSVDP(frameData, cru, flicker);
|
||||
}
|
||||
|
||||
getVCounter() {
|
||||
var y = this.currentScanline;
|
||||
return (y <= 0xda) ? (y) : (y - 6);
|
||||
}
|
||||
getHCounter() {
|
||||
return this.latchedHCounter;
|
||||
}
|
||||
computeHCounter() {
|
||||
var t0 = this.startLineTstates;
|
||||
var t1 = this.cpu.getTstates();
|
||||
return (t1-t0) & 0xff; // TODO
|
||||
}
|
||||
setIOPortControl(v:number) {
|
||||
if ((v ^ this.ioControlFlags) & 0xa0) { // either joystick TH pin
|
||||
this.latchedHCounter = this.computeHCounter();
|
||||
//console.log("H:"+hex(this.latchedHCounter)+" V:"+hex(this.getVCounter()));
|
||||
}
|
||||
this.ioControlFlags = v;
|
||||
}
|
||||
|
||||
newRAM() {
|
||||
return new Uint8Array(0x2000);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
class SMSPlatform extends BaseZ80MachinePlatform<SMS> implements Platform {
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0xc000, 0xffff, 0x1fff, (a) => { return this.ram[a]; }],
|
||||
[0x0000, 0x03ff, 0x3ff, (a) => { return this.rom[a]; }],
|
||||
[0x0400, 0x3fff, 0x3fff, (a) => { return this.getPagedROM(a,1); }],
|
||||
[0x4000, 0x7fff, 0x3fff, (a) => { return this.getPagedROM(a,2); }],
|
||||
[0x8000, 0xbfff, 0x3fff, (a) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
return this.cartram[(reg0 & 0x4) ? a+0x4000 : a];
|
||||
} else {
|
||||
return this.getPagedROM(a,3);
|
||||
}
|
||||
}],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0xc000, 0xfffb, 0x1fff, (a,v) => {
|
||||
this.ram[a] = v;
|
||||
}],
|
||||
[0xfffc, 0xffff, 0x3, (a,v) => {
|
||||
this.pagingRegisters[a] = v;
|
||||
this.ram[a+0x1ffc] = v;
|
||||
}],
|
||||
[0x8000, 0xbfff, 0x3fff, (a,v) => {
|
||||
var reg0 = this.pagingRegisters[0]; // RAM select?
|
||||
if (reg0 & 0x8) {
|
||||
if (this.cartram.length == 0)
|
||||
this.cartram = new Uint8Array(0x8000); // create cartridge RAM lazily
|
||||
this.cartram[(reg0 & 0x4) ? a+0x4000 : a] = v;
|
||||
}
|
||||
}],
|
||||
]),
|
||||
isContended: () => { return false; },
|
||||
};
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
if (data.length <= 0xc000) {
|
||||
this.rom = padBytes(data, 0xc000);
|
||||
this.romPageMask = 3; // only pages 0, 1, 2
|
||||
} else {
|
||||
switch (data.length) {
|
||||
case 0x10000:
|
||||
case 0x20000:
|
||||
case 0x40000:
|
||||
case 0x80000:
|
||||
this.rom = data;
|
||||
this.romPageMask = (data.length >> 14) - 1; // div $4000
|
||||
break;
|
||||
default:
|
||||
throw "Unknown rom size: $" + hex(data.length);
|
||||
}
|
||||
}
|
||||
//console.log("romPageMask: " + hex(this.romPageMask));
|
||||
this.reset();
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.pagingRegisters.set(state.pr);
|
||||
this.cartram.set(state.cr);
|
||||
this.latchedHCounter = state.lhc;
|
||||
this.ioControlFlags = state.iocf;
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['pr'] = this.pagingRegisters.slice(0);
|
||||
state['cr'] = this.cartram.slice(0);
|
||||
state['lhc'] = this.latchedHCounter;
|
||||
state['iocf'] = this.ioControlFlags;
|
||||
return state;
|
||||
}
|
||||
getDebugInfo(category, state) {
|
||||
switch (category) {
|
||||
case 'CPU':
|
||||
return super.getDebugInfo(category, state) +
|
||||
"\nBank Regs: " + this.pagingRegisters + "\n";
|
||||
default: return super.getDebugInfo(category, state);
|
||||
}
|
||||
}
|
||||
newMachine() { return new SMS(); }
|
||||
getPresets() { return SMS_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
readVRAMAddress(a) { return this.machine.readVRAMAddress(a); }
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BaseZ80Platform } from "../baseplatform";
|
||||
import { Z80, Z80State } from "../cpu/ZilogZ80";
|
||||
import { BasicMachine, CPU, Bus } from "../devices";
|
||||
import { Platform, BaseZ80MachinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex } from "../util";
|
||||
import { SampleAudio } from "../audio";
|
||||
|
||||
var WILLIAMS_SOUND_PRESETS = [
|
||||
{id:'swave.c', name:'Wavetable Synth'},
|
||||
{ id: 'swave.c', name: 'Wavetable Synth' },
|
||||
];
|
||||
|
||||
/****************************************************************************
|
||||
|
@ -33,125 +34,89 @@ var WILLIAMS_SOUND_PRESETS = [
|
|||
|
||||
****************************************************************************/
|
||||
|
||||
var WilliamsSoundPlatform = function(mainElement) {
|
||||
var self = this;
|
||||
this.__proto__ = new (BaseZ80Platform as any)();
|
||||
class WilliamsSound extends BasicMachine {
|
||||
cpuFrequency = 18432000 / 6; // 3.072 MHz
|
||||
cpuCyclesPerFrame = this.cpuFrequency / 60;
|
||||
cpuAudioFactor = 32;
|
||||
canvasWidth = 256;
|
||||
numVisibleScanlines = 256;
|
||||
defaultROMSize = 0x4000;
|
||||
sampleRate = this.cpuFrequency;
|
||||
overscan = true;
|
||||
|
||||
cpu : Z80;
|
||||
ram = new Uint8Array(0x400);
|
||||
iobus : Bus;
|
||||
|
||||
command : number = 0;
|
||||
dac : number = 0;
|
||||
dac_float : number = 0;
|
||||
xpos : number = 0;
|
||||
|
||||
var cpu, ram, rom, membus, iobus;
|
||||
var audio, master;
|
||||
var video, timer;
|
||||
var command = 0;
|
||||
var dac = 0;
|
||||
var dac_float = 0.0
|
||||
var current_buffer;
|
||||
var last_tstate;
|
||||
var pixels;
|
||||
read = newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0x3fff, (a) => { return this.rom && this.rom[a]; }],
|
||||
[0x4000, 0x7fff, 0x3ff, (a) => { return this.ram[a]; }]
|
||||
]);
|
||||
|
||||
var cpuFrequency = 18432000/6; // 3.072 MHz
|
||||
var cpuCyclesPerFrame = cpuFrequency/60;
|
||||
var cpuAudioFactor = 32;
|
||||
|
||||
function fillBuffer() {
|
||||
var t = cpu.getTstates() / cpuAudioFactor;
|
||||
while (last_tstate < t) {
|
||||
current_buffer[last_tstate++] = dac_float;
|
||||
write = newAddressDecoder([
|
||||
[0x4000, 0x7fff, 0x3ff, (a, v) => { this.ram[a] = v; }],
|
||||
]);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.cpu = new Z80();
|
||||
this.connectCPUMemoryBus(this);
|
||||
this.connectCPUIOBus({
|
||||
read: (addr) => {
|
||||
return this.command & 0xff;
|
||||
},
|
||||
write: (addr, val) => {
|
||||
let dac = this.dac = val & 0xff;
|
||||
this.dac_float = ((dac & 0x80) ? -256 + dac : dac) / 128.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
advanceFrame(trap) : number {
|
||||
this.pixels && this.pixels.fill(0); // clear waveform
|
||||
let maxCycles = this.cpuCyclesPerFrame;
|
||||
var n = 0;
|
||||
while (n < maxCycles) {
|
||||
if (trap && trap()) {
|
||||
break;
|
||||
}
|
||||
n += this.advanceCPU();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
advanceCPU() {
|
||||
var n = super.advanceCPU();
|
||||
this.audio && this.audio.feedSample(this.dac_float, n);
|
||||
// draw waveform on screen
|
||||
if (this.pixels && !this.cpu.isHalted()) {
|
||||
this.pixels[((this.xpos >> 8) & 0xff) + ((255-this.dac) << 8)] = 0xff33ff33;
|
||||
this.xpos = (this.xpos + n) & 0xffffff;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
this.getPresets = function() {
|
||||
return WILLIAMS_SOUND_PRESETS;
|
||||
}
|
||||
|
||||
this.start = function() {
|
||||
ram = new RAM(0x400);
|
||||
membus = {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x3fff, 0x3fff, function(a) { return rom ? rom[a] : null; }],
|
||||
[0x4000, 0x7fff, 0x3ff, function(a) { return ram.mem[a]; }]
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x4000, 0x7fff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
|
||||
]),
|
||||
};
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return command & 0xff;
|
||||
},
|
||||
write: function(addr, val) {
|
||||
dac = val & 0xff;
|
||||
dac_float = ((dac & 0x80) ? -256+dac : dac) / 128.0;
|
||||
fillBuffer();
|
||||
}
|
||||
};
|
||||
this.readAddress = membus.read;
|
||||
cpu = this.newCPU(membus, iobus);
|
||||
audio = new SampleAudio(cpuFrequency / cpuAudioFactor);
|
||||
audio.callback = function(lbuf) {
|
||||
if (self.isRunning()) {
|
||||
cpu.setTstates(0);
|
||||
current_buffer = lbuf;
|
||||
last_tstate = 0;
|
||||
self.runCPU(cpu, lbuf.length * cpuAudioFactor);
|
||||
cpu.setTstates(lbuf.length * cpuAudioFactor); // TODO?
|
||||
fillBuffer();
|
||||
for (var i=0; i<256; i++) {
|
||||
var y = Math.round((current_buffer[i] * 127) + 128);
|
||||
pixels[i + y*256] = 0xff33ff33;
|
||||
}
|
||||
}
|
||||
};
|
||||
video = new RasterVideo(mainElement,256,256);
|
||||
video.create();
|
||||
video.setKeyboardEvents(function(key,code,flags) {
|
||||
var intr = (key-49);
|
||||
if (intr >= 0 && (flags & 1)) {
|
||||
command = intr & 0xff;
|
||||
cpu.reset();
|
||||
}
|
||||
});
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(30, function() {
|
||||
if (self.isRunning()) {
|
||||
video.updateFrame();
|
||||
pixels.fill(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
rom = padBytes(data, 0x4000);
|
||||
cpu.reset();
|
||||
}
|
||||
|
||||
this.loadState = function(state) {
|
||||
cpu.loadState(state.c);
|
||||
ram.mem.set(state.b);
|
||||
}
|
||||
this.saveState = function() {
|
||||
return {
|
||||
c:self.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
};
|
||||
}
|
||||
this.getCPUState = function() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
this.resume = function() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
this.reset = function() {
|
||||
cpu.reset();
|
||||
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
||||
setKeyInput(key:number, code:number, flags:number) : void {
|
||||
var intr = (key - 49);
|
||||
if (intr >= 0 && (flags & 1)) {
|
||||
this.command = intr & 0xff;
|
||||
this.cpu.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class WilliamsSoundPlatform extends BaseZ80MachinePlatform<WilliamsSound> {
|
||||
|
||||
newMachine() { return new WilliamsSound(); }
|
||||
getPresets() { return WILLIAMS_SOUND_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
|
||||
}
|
||||
|
||||
PLATFORMS['sound_williams-z80'] = WilliamsSoundPlatform;
|
||||
|
|
|
@ -209,6 +209,8 @@ class VCSPlatform extends BasePlatform {
|
|||
// TODO: shouldn't have to do this when debugging
|
||||
if (this.lastBreakState && addr >= 0x80 && addr < 0x100)
|
||||
return this.getRAMForState(this.lastBreakState)[addr & 0x7f];
|
||||
else if ((addr & 0x1280) === 0x280)
|
||||
return 0; // don't read PIA
|
||||
else
|
||||
return Javatari.room.console.readAddress(addr);
|
||||
}
|
||||
|
@ -304,6 +306,12 @@ class VCSPlatform extends BasePlatform {
|
|||
window.open("https://alienbill.com/2600/101/docs/stella.html", "_help"); // TODO
|
||||
}
|
||||
|
||||
getMemoryMap = function() { return {main:[
|
||||
{name:'TIA Registers',start:0x00,size:0x80,type:'io'},
|
||||
{name:'PIA RAM',start:0x80,size:0x80,type:'ram'},
|
||||
{name:'PIA Ports and Timer',start:0x280,size:0x18,type:'io'},
|
||||
{name:'Cartridge ROM',start:0xf000,size:0x1000,type:'rom'},
|
||||
]}};
|
||||
};
|
||||
|
||||
// TODO: mixin for Base6502Platform?
|
||||
|
@ -344,6 +352,7 @@ class VCSMAMEPlatform extends BaseMAMEPlatform implements Platform {
|
|||
getOriginPC = function() {
|
||||
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
////////////////
|
||||
|
|
|
@ -172,6 +172,11 @@ var AtariVectorPlatform = function(mainElement) {
|
|||
this.getCPUState = function() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
this.getMemoryMap = function() { return { main:[
|
||||
{name:'Switches/POKEY I/O',start:0x7800,size:0x1000,type:'io'},
|
||||
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
||||
{name:'EAROM',start:0x8900,size:0x100,type:'ram'},
|
||||
] } };
|
||||
}
|
||||
|
||||
var AtariColorVectorPlatform = function(mainElement) {
|
||||
|
@ -389,7 +394,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
|||
|
||||
};
|
||||
this.readAddress = bus.read;
|
||||
cpu = this.newCPU(bus);
|
||||
cpu = this.newCPU(bus, bus);
|
||||
// create video/audio
|
||||
video = new VectorVideo(mainElement,1024,1024);
|
||||
dvg = new DVGColorStateMachine(bus, video, 0xa000);
|
||||
|
@ -402,7 +407,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
|||
this.advance = (novideo) => {
|
||||
if (!novideo) video.clear();
|
||||
this.runCPU(cpu, cpuCyclesPerFrame);
|
||||
cpu.requestInterrupt();
|
||||
cpu.interrupt(0xff); // RST 0x38
|
||||
switches[0xf] = (switches[0xf] + 1) & 0x3;
|
||||
if (--switches[0xe] <= 0) {
|
||||
console.log("WATCHDOG FIRED"); // TODO: alert on video
|
||||
|
@ -458,6 +463,12 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
|||
this.getCPUState = function() {
|
||||
return cpu.saveState();
|
||||
}
|
||||
this.getMemoryMap = function() { return { main:[
|
||||
{name:'Switches/POKEY I/O',start:0x8000,size:0x100,type:'io'},
|
||||
{name:'Math Box I/O',start:0x8100,size:0x100,type:'io'},
|
||||
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
||||
{name:'DVG RAM',start:0xa000,size:0x4000,type:'ram'},
|
||||
] } };
|
||||
}
|
||||
|
||||
// DIGITAL VIDEO GENERATOR
|
||||
|
|
|
@ -94,6 +94,9 @@ export function VL_LTES_III(x,lbits,y,lhs,rhs) {
|
|||
export function VL_GTES_III(x,lbits,y,lhs,rhs) {
|
||||
return (VL_EXTENDS_II(x,lbits,lhs) >= VL_EXTENDS_II(x,lbits,rhs)) ? 1 : 0; }
|
||||
|
||||
export function VL_DIV_III(lbits,lhs,rhs) {
|
||||
return (((rhs)==0)?0:(lhs)/(rhs)); }
|
||||
|
||||
export function VL_MODDIV_III(lbits,lhs,rhs) {
|
||||
return (((rhs)==0)?0:(lhs)%(rhs)); }
|
||||
|
||||
|
@ -349,10 +352,6 @@ var VerilogPlatform = function(mainElement, options) {
|
|||
start() {
|
||||
video = new RasterVideo(mainElement,videoWidth,videoHeight,{overscan:true});
|
||||
video.create();
|
||||
var ctx = video.getContext();
|
||||
ctx.font = "8px TinyFont";
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textAlign = "left";
|
||||
poller = setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP, (o,key,code,flags) => {
|
||||
if (flags & KeyFlags.KeyPress) {
|
||||
keycode = code | 0x80;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BasicZ80ScanlinePlatform } from "../baseplatform";
|
||||
import { PLATFORMS, newAddressDecoder, padBytes, Keys, makeKeycodeMap } from "../emu";
|
||||
import { MasterAudio, AY38910_Audio } from "../audio";
|
||||
import { VicDual } from "../machine/vicdual";
|
||||
import { BaseZ80MachinePlatform } from "../baseplatform";
|
||||
import { Platform } from "../baseplatform";
|
||||
import { PLATFORMS } from "../emu";
|
||||
|
||||
const VICDUAL_PRESETS = [
|
||||
{ id: 'minimal.c', name: 'Minimal Example' },
|
||||
|
@ -14,185 +14,17 @@ const VICDUAL_PRESETS = [
|
|||
{ id: 'music.c', name: 'Music Player' },
|
||||
];
|
||||
|
||||
class VicDualDisplay {
|
||||
palbank: number = 0;
|
||||
class VicDualPlatform extends BaseZ80MachinePlatform<VicDual> implements Platform {
|
||||
|
||||
palette = [
|
||||
0xff000000, // black
|
||||
0xff0000ff, // red
|
||||
0xff00ff00, // green
|
||||
0xff00ffff, // yellow
|
||||
0xffff0000, // blue
|
||||
0xffff00ff, // magenta
|
||||
0xffffff00, // cyan
|
||||
0xffffffff // white
|
||||
];
|
||||
|
||||
// default PROM
|
||||
colorprom = [
|
||||
0xe0, 0x60, 0x20, 0x60, 0xc0, 0x60, 0x40, 0xc0,
|
||||
0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0, 0x0e,
|
||||
0xe0, 0xe0, 0xe0, 0xe0, 0x60, 0x60, 0x60, 0x60,
|
||||
0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
|
||||
];
|
||||
|
||||
// videoram 0xc000-0xc3ff
|
||||
// RAM 0xc400-0xc7ff
|
||||
// charram 0xc800-0xcfff
|
||||
drawScanline(ram, pixels: Uint32Array, sl: number) {
|
||||
if (sl >= 224) return;
|
||||
var pixofs = sl * 256;
|
||||
var outi = pixofs; // starting output pixel in frame buffer
|
||||
var vramofs = (sl >> 3) << 5; // offset in VRAM
|
||||
var yy = sl & 7; // y offset within tile
|
||||
for (var xx = 0; xx < 32; xx++) {
|
||||
var code = ram[vramofs + xx];
|
||||
var data = ram[0x800 + (code << 3) + yy];
|
||||
var col = (code >> 5) + (this.palbank << 3);
|
||||
var color1 = this.palette[(this.colorprom[col] >> 1) & 7];
|
||||
var color2 = this.palette[(this.colorprom[col] >> 5) & 7];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var bm = 128 >> i;
|
||||
pixels[outi] = (data & bm) ? color2 : color1;
|
||||
/* TODO
|
||||
if (framestats) {
|
||||
framestats.layers.tiles[outi] = (data&bm) ? colorprom[col+8] : colorprom[col];
|
||||
}
|
||||
*/
|
||||
outi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CARNIVAL_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.A, 2, -0x20],
|
||||
[Keys.B, 2, -0x40],
|
||||
[Keys.LEFT, 1, -0x10],
|
||||
[Keys.RIGHT, 1, -0x20],
|
||||
[Keys.UP, 1, -0x40],
|
||||
[Keys.DOWN, 1, -0x80],
|
||||
[Keys.START, 2, -0x10],
|
||||
[Keys.P2_START, 3, -0x20],
|
||||
[Keys.SELECT, 3, 0x8],
|
||||
]);
|
||||
|
||||
const XTAL = 15468000.0;
|
||||
const scanlinesPerFrame = 0x106;
|
||||
const vblankStart = 0xe0;
|
||||
const vsyncStart = 0xec;
|
||||
const vsyncEnd = 0xf0;
|
||||
const cpuFrequency = XTAL / 8;
|
||||
const hsyncFrequency = XTAL / 3 / scanlinesPerFrame;
|
||||
const vsyncFrequency = hsyncFrequency / 0x148;
|
||||
const cpuCyclesPerLine = cpuFrequency / hsyncFrequency;
|
||||
const timerFrequency = 500; // input 2 bit 0x8
|
||||
const cyclesPerTimerTick = cpuFrequency / (2 * timerFrequency);
|
||||
|
||||
class VicDualPlatform extends BasicZ80ScanlinePlatform implements Platform {
|
||||
|
||||
display: VicDualDisplay;
|
||||
psg;
|
||||
reset_disable = false;
|
||||
reset_disable_timer;
|
||||
|
||||
cpuFrequency = XTAL / 8; // MHz
|
||||
canvasWidth = 256;
|
||||
numTotalScanlines = 262;
|
||||
numVisibleScanlines = 224;
|
||||
defaultROMSize = 0x4040;
|
||||
|
||||
getPresets() { return VICDUAL_PRESETS; }
|
||||
|
||||
getKeyboardMap() { return CARNIVAL_KEYCODE_MAP; }
|
||||
|
||||
getKeyboardFunction() {
|
||||
return (o) => {
|
||||
// reset when coin inserted
|
||||
if (o.index == 3 && o.mask == 0x8 && !this.reset_disable) {
|
||||
this.cpu.reset();
|
||||
console.log("coin inserted");
|
||||
console.log(this.inputs)
|
||||
}
|
||||
// don't allow repeated resets in short period of time
|
||||
this.reset_disable = true;
|
||||
clearTimeout(this.reset_disable_timer);
|
||||
this.reset_disable_timer = setTimeout(() => { this.reset_disable = false; }, 1100);
|
||||
}
|
||||
};
|
||||
|
||||
getVideoOptions() { return { rotate: -90 }; }
|
||||
|
||||
newRAM() {
|
||||
return new Uint8Array(0x1000);
|
||||
}
|
||||
|
||||
newMembus() {
|
||||
return {
|
||||
read: newAddressDecoder([
|
||||
[0x0000, 0x7fff, 0x3fff, (a) => { return this.rom ? this.rom[a] : null; }],
|
||||
[0x8000, 0xffff, 0x0fff, (a) => { return this.ram[a]; }],
|
||||
]),
|
||||
write: newAddressDecoder([
|
||||
[0x8000, 0xffff, 0x0fff, (a, v) => { this.ram[a] = v; }],
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
newIOBus() {
|
||||
return {
|
||||
read: (addr) => {
|
||||
return this.inputs[addr & 3];
|
||||
},
|
||||
write: (addr, val) => {
|
||||
if (addr & 0x1) { this.psg.selectRegister(val & 0xf); }; // audio 1
|
||||
if (addr & 0x2) { this.psg.setData(val); }; // audio 2
|
||||
if (addr & 0x8) { }; // TODO: assert coin status
|
||||
if (addr & 0x40) { this.display.palbank = val & 3; }; // palette
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
|
||||
this.display = new VicDualDisplay();
|
||||
this.audio = new MasterAudio();
|
||||
this.psg = new AY38910_Audio(this.audio);
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.psg.reset();
|
||||
}
|
||||
|
||||
startScanline(sl: number) {
|
||||
this.inputs[2] &= ~0x8;
|
||||
this.inputs[2] |= ((this.cpu.getTstates() / cyclesPerTimerTick) & 1) << 3;
|
||||
if (sl == vblankStart) this.inputs[1] |= 0x8;
|
||||
if (sl == vsyncEnd) this.inputs[1] &= ~0x8;
|
||||
}
|
||||
|
||||
drawScanline(sl: number) {
|
||||
this.display.drawScanline(this.ram, this.video.getFrameData(), sl);
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
super.loadROM(title, data);
|
||||
if (data.length >= 0x4020 && (data[0x4000] || data[0x401f])) {
|
||||
this.display.colorprom = data.slice(0x4000, 0x4020);
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
super.loadState(state);
|
||||
this.display.palbank = state.pb;
|
||||
}
|
||||
saveState() {
|
||||
var state = super.saveState();
|
||||
state['pb'] = this.display.palbank;
|
||||
return state;
|
||||
}
|
||||
newMachine() { return new VicDual(); }
|
||||
getPresets() { return VICDUAL_PRESETS; }
|
||||
getDefaultExtension() { return ".c"; };
|
||||
readAddress(a) { return this.machine.read(a); }
|
||||
// TODO loadBios(bios) { this.machine.loadBIOS(a); }
|
||||
getMemoryMap = function() { return { main:[
|
||||
{name:'Cell RAM',start:0xe000,size:32*32,type:'ram'},
|
||||
{name:'Tile RAM',start:0xe800,size:256*8,type:'ram'},
|
||||
] } };
|
||||
}
|
||||
|
||||
PLATFORMS['vicdual'] = VicDualPlatform;
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BaseZ80Platform, Base6809Platform } from "../baseplatform";
|
||||
import { Platform, BaseZ80Platform, Base6809Platform } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu";
|
||||
import { hex } from "../util";
|
||||
import { MasterAudio, WorkerSoundChannel } from "../audio";
|
||||
|
||||
var WILLIAMS_PRESETS = [
|
||||
{id:'gfxtest.c', name:'Graphics Test'},
|
||||
{id:'sprites.c', name:'Sprite Test'},
|
||||
{id:'game1.c', name:'Raster Paranoia Game'},
|
||||
{id:'bitmap_rle.c', name:'RLE Bitmap'},
|
||||
{ id: 'gfxtest.c', name: 'Graphics Test' },
|
||||
{ id: 'sprites.c', name: 'Sprite Test' },
|
||||
{ id: 'game1.c', name: 'Raster Paranoia Game' },
|
||||
{ id: 'bitmap_rle.c', name: 'RLE Bitmap' },
|
||||
];
|
||||
|
||||
var WilliamsPlatform = function(mainElement, proto) {
|
||||
var self = this;
|
||||
this.__proto__ = new (proto?proto:Base6809Platform)();
|
||||
this.__proto__ = new (proto ? proto : Base6809Platform)();
|
||||
|
||||
var SCREEN_HEIGHT = 304;
|
||||
var SCREEN_WIDTH = 256;
|
||||
|
@ -35,8 +34,8 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
var audio, worker, workerchannel;
|
||||
|
||||
var xtal = 12000000;
|
||||
var cpuFrequency = xtal/3/4;
|
||||
var cpuCyclesPerFrame = cpuFrequency/60; // TODO
|
||||
var cpuFrequency = xtal / 3 / 4;
|
||||
var cpuCyclesPerFrame = cpuFrequency / 60; // TODO
|
||||
var cpuScale = 1;
|
||||
var INITIAL_WATCHDOG = 8;
|
||||
var PIXEL_ON = 0xffeeeeee;
|
||||
|
@ -59,26 +58,26 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
]);
|
||||
|
||||
var ROBOTRON_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.P2_UP, 0, 0x1],
|
||||
[Keys.P2_DOWN, 0, 0x2],
|
||||
[Keys.P2_LEFT, 0, 0x4],
|
||||
[Keys.P2_UP, 0, 0x1],
|
||||
[Keys.P2_DOWN, 0, 0x2],
|
||||
[Keys.P2_LEFT, 0, 0x4],
|
||||
[Keys.P2_RIGHT, 0, 0x8],
|
||||
[Keys.START, 0, 0x10],
|
||||
[Keys.START, 0, 0x10],
|
||||
[Keys.P2_START, 0, 0x20],
|
||||
[Keys.UP, 0, 0x40],
|
||||
[Keys.DOWN, 0, 0x80],
|
||||
[Keys.LEFT, 2, 0x1],
|
||||
[Keys.RIGHT, 2, 0x2],
|
||||
[Keys.UP, 0, 0x40],
|
||||
[Keys.DOWN, 0, 0x80],
|
||||
[Keys.LEFT, 2, 0x1],
|
||||
[Keys.RIGHT, 2, 0x2],
|
||||
[Keys.VK_7, 4, 0x1],
|
||||
[Keys.VK_8, 4, 0x2],
|
||||
[Keys.VK_6, 4, 0x4],
|
||||
[Keys.VK_9, 4, 0x8],
|
||||
[Keys.SELECT, 4, 0x10],
|
||||
[Keys.SELECT, 4, 0x10],
|
||||
]);
|
||||
// TODO: sound board handshake
|
||||
|
||||
var palette = [];
|
||||
for (var ii=0; ii<16; ii++)
|
||||
for (var ii = 0; ii < 16; ii++)
|
||||
palette[ii] = 0xff000000;
|
||||
|
||||
this.getPresets = function() {
|
||||
|
@ -89,17 +88,17 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
|
||||
var ioread_defender = newAddressDecoder([
|
||||
[0x400, 0x5ff, 0x1ff, function(a) { return nvram.mem[a]; }],
|
||||
[0x800, 0x800, 0, function(a) { return video_counter; }],
|
||||
[0xc00, 0xc07, 0x7, function(a) { return pia6821[a]; }],
|
||||
[0x0, 0xfff, 0, function(a) { /*console.log('ioread',hex(a));*/ }],
|
||||
[0x800, 0x800, 0, function(a) { return video_counter; }],
|
||||
[0xc00, 0xc07, 0x7, function(a) { return pia6821[a]; }],
|
||||
[0x0, 0xfff, 0, function(a) { /*console.log('ioread',hex(a));*/ }],
|
||||
]);
|
||||
|
||||
var iowrite_defender = newAddressDecoder([
|
||||
[0x0, 0xf, 0xf, setPalette],
|
||||
[0x3fc, 0x3ff, 0, function(a,v) { if (v == 0x38) watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x400, 0x5ff, 0x1ff, function(a,v) { nvram.mem[a] = v; }],
|
||||
[0xc00, 0xc07, 0x7, function(a,v) { pia6821[a] = v; }],
|
||||
[0x0, 0xfff, 0, function(a,v) { console.log('iowrite',hex(a),hex(v)); }],
|
||||
[0x0, 0xf, 0xf, setPalette],
|
||||
[0x3fc, 0x3ff, 0, function(a, v) { if (v == 0x38) watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0x400, 0x5ff, 0x1ff, function(a, v) { nvram.mem[a] = v; }],
|
||||
[0xc00, 0xc07, 0x7, function(a, v) { pia6821[a] = v; }],
|
||||
[0x0, 0xfff, 0, function(a, v) { console.log('iowrite', hex(a), hex(v)); }],
|
||||
]);
|
||||
|
||||
var memread_defender = newAddressDecoder([
|
||||
|
@ -107,43 +106,43 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
[0xc000, 0xcfff, 0x0fff, function(a) {
|
||||
switch (banksel) {
|
||||
case 0: return ioread_defender(a);
|
||||
case 1: return rom[a+0x3000];
|
||||
case 2: return rom[a+0x4000];
|
||||
case 3: return rom[a+0x5000];
|
||||
case 7: return rom[a+0x6000];
|
||||
case 1: return rom[a + 0x3000];
|
||||
case 2: return rom[a + 0x4000];
|
||||
case 3: return rom[a + 0x5000];
|
||||
case 7: return rom[a + 0x6000];
|
||||
default: return 0; // TODO: error light
|
||||
}
|
||||
}],
|
||||
[0xd000, 0xffff, 0xffff, function(a) { return rom ? rom[a-0xd000] : 0; }],
|
||||
[0xd000, 0xffff, 0xffff, function(a) { return rom ? rom[a - 0xd000] : 0; }],
|
||||
]);
|
||||
|
||||
var memwrite_defender = newAddressDecoder([
|
||||
[0x0000, 0x97ff, 0, write_display_byte],
|
||||
[0x9800, 0xbfff, 0, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x0000, 0x97ff, 0, write_display_byte],
|
||||
[0x9800, 0xbfff, 0, function(a, v) { ram.mem[a] = v; }],
|
||||
[0xc000, 0xcfff, 0x0fff, iowrite_defender],
|
||||
[0xd000, 0xdfff, 0, function(a,v) { banksel = v&0x7; }],
|
||||
[0, 0xffff, 0, function(a,v) { console.log(hex(a), hex(v)); }],
|
||||
[0xd000, 0xdfff, 0, function(a, v) { banksel = v & 0x7; }],
|
||||
[0, 0xffff, 0, function(a, v) { console.log(hex(a), hex(v)); }],
|
||||
]);
|
||||
|
||||
// Robotron, Joust, Bubbles, Stargate
|
||||
|
||||
var ioread_williams = newAddressDecoder([
|
||||
[0x804, 0x807, 0x3, function(a) { return pia6821[a]; }],
|
||||
[0x80c, 0x80f, 0x3, function(a) { return pia6821[a+4]; }],
|
||||
[0xb00, 0xbff, 0, function(a) { return video_counter; }],
|
||||
[0x804, 0x807, 0x3, function(a) { return pia6821[a]; }],
|
||||
[0x80c, 0x80f, 0x3, function(a) { return pia6821[a + 4]; }],
|
||||
[0xb00, 0xbff, 0, function(a) { return video_counter; }],
|
||||
[0xc00, 0xfff, 0x3ff, function(a) { return nvram.mem[a]; }],
|
||||
[0x0, 0xfff, 0, function(a) { /* console.log('ioread',hex(a)); */ }],
|
||||
[0x0, 0xfff, 0, function(a) { /* console.log('ioread',hex(a)); */ }],
|
||||
]);
|
||||
|
||||
var iowrite_williams = newAddressDecoder([
|
||||
[0x0, 0xf, 0xf, setPalette],
|
||||
[0x80c, 0x80c, 0xf, function(a,v) { if (worker) worker.postMessage({command:v}); }],
|
||||
[0x0, 0xf, 0xf, setPalette],
|
||||
[0x80c, 0x80c, 0xf, function(a, v) { if (worker) worker.postMessage({ command: v }); }],
|
||||
//[0x804, 0x807, 0x3, function(a,v) { console.log('iowrite',a); }], // TODO: sound
|
||||
//[0x80c, 0x80f, 0x3, function(a,v) { console.log('iowrite',a+4); }], // TODO: sound
|
||||
[0x900, 0x9ff, 0, function(a,v) { banksel = v & 0x1; }],
|
||||
[0xa00, 0xa07, 0x7, setBlitter],
|
||||
[0xbff, 0xbff, 0, function(a,v) { if (v == 0x39) watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0xc00, 0xfff, 0x3ff, function(a,v) { nvram.mem[a] = v; }],
|
||||
[0x900, 0x9ff, 0, function(a, v) { banksel = v & 0x1; }],
|
||||
[0xa00, 0xa07, 0x7, setBlitter],
|
||||
[0xbff, 0xbff, 0, function(a, v) { if (v == 0x39) watchdog_counter = INITIAL_WATCHDOG; }],
|
||||
[0xc00, 0xfff, 0x3ff, function(a, v) { nvram.mem[a] = v; }],
|
||||
//[0x0, 0xfff, 0, function(a,v) { console.log('iowrite',hex(a),hex(v)); }],
|
||||
]);
|
||||
|
||||
|
@ -151,45 +150,45 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
[0x0000, 0x8fff, 0xffff, function(a) { return banksel ? rom[a] : ram.mem[a]; }],
|
||||
[0x9000, 0xbfff, 0xffff, function(a) { return ram.mem[a]; }],
|
||||
[0xc000, 0xcfff, 0x0fff, ioread_williams],
|
||||
[0xd000, 0xffff, 0xffff, function(a) { return rom ? rom[a-0x4000] : 0; }],
|
||||
[0xd000, 0xffff, 0xffff, function(a) { return rom ? rom[a - 0x4000] : 0; }],
|
||||
]);
|
||||
|
||||
var memwrite_williams = newAddressDecoder([
|
||||
[0x0000, 0x97ff, 0, write_display_byte],
|
||||
[0x9800, 0xbfff, 0, function(a,v) { ram.mem[a] = v; }],
|
||||
[0x0000, 0x97ff, 0, write_display_byte],
|
||||
[0x9800, 0xbfff, 0, function(a, v) { ram.mem[a] = v; }],
|
||||
[0xc000, 0xcfff, 0x0fff, iowrite_williams],
|
||||
//[0x0000, 0xffff, 0, function(a,v) { console.log(hex(a), hex(v)); }],
|
||||
]);
|
||||
|
||||
// d1d6 ldu $11 / beq $d1ed
|
||||
|
||||
function setPalette(a,v) {
|
||||
function setPalette(a, v) {
|
||||
// RRRGGGBB
|
||||
var color = 0xff000000 | ((v&7)<<5) | (((v>>3)&7)<<13) | (((v>>6)<<22));
|
||||
var color = 0xff000000 | ((v & 7) << 5) | (((v >> 3) & 7) << 13) | (((v >> 6) << 22));
|
||||
if (color != palette[a]) {
|
||||
palette[a] = color;
|
||||
screenNeedsRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
function write_display_byte(a:number,v:number) {
|
||||
function write_display_byte(a: number, v: number) {
|
||||
ram.mem[a] = v;
|
||||
drawDisplayByte(a, v);
|
||||
if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter
|
||||
}
|
||||
|
||||
function drawDisplayByte(a,v) {
|
||||
var ofs = ((a&0xff00)<<1) | ((a&0xff)^0xff);
|
||||
pixels[ofs] = palette[v>>4];
|
||||
pixels[ofs+256] = palette[v&0xf];
|
||||
function drawDisplayByte(a, v) {
|
||||
var ofs = ((a & 0xff00) << 1) | ((a & 0xff) ^ 0xff);
|
||||
pixels[ofs] = palette[v >> 4];
|
||||
pixels[ofs + 256] = palette[v & 0xf];
|
||||
}
|
||||
|
||||
function setBlitter(a,v) {
|
||||
function setBlitter(a, v) {
|
||||
if (a) {
|
||||
blitregs[a] = v;
|
||||
} else {
|
||||
var cycles = doBlit(v);
|
||||
cpu.setTstates(cpu.getTstates() + cycles * cpuScale);
|
||||
this.waitCycles -= cycles * cpuScale; // wait CPU cycles
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,9 +200,9 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
var dstart = (blitregs[4] << 8) + blitregs[5];
|
||||
var w = blitregs[6] ^ 4; // blitter bug fix
|
||||
var h = blitregs[7] ^ 4;
|
||||
if(w==0) w++;
|
||||
if(h==0) h++;
|
||||
if(h==255) h++;
|
||||
if (w == 0) w++;
|
||||
if (h == 0) h++;
|
||||
if (h == 255) h++;
|
||||
var sxinc = (flags & 0x1) ? 256 : 1;
|
||||
var syinc = (flags & 0x1) ? 1 : w;
|
||||
var dxinc = (flags & 0x2) ? 256 : 1;
|
||||
|
@ -234,7 +233,7 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
else
|
||||
sstart += syinc;
|
||||
}
|
||||
return w * h * (2 + ((flags&0x4)>>2)); // # of memory accesses
|
||||
return w * h * (2 + ((flags & 0x4) >> 2)); // # of memory accesses
|
||||
}
|
||||
|
||||
function blit_pixel(dstaddr, srcdata, flags) {
|
||||
|
@ -242,19 +241,19 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
var solid = blitregs[1];
|
||||
var keepmask = 0xff; //what part of original dst byte should be kept, based on NO_EVEN and NO_ODD flags
|
||||
//even pixel (D7-D4)
|
||||
if((flags & 0x8) && !(srcdata & 0xf0)) { //FG only and src even pixel=0
|
||||
if(flags & 0x80) keepmask &= 0x0f; // no even
|
||||
if ((flags & 0x8) && !(srcdata & 0xf0)) { //FG only and src even pixel=0
|
||||
if (flags & 0x80) keepmask &= 0x0f; // no even
|
||||
} else {
|
||||
if(!(flags & 0x80)) keepmask &= 0x0f; // not no even
|
||||
if (!(flags & 0x80)) keepmask &= 0x0f; // not no even
|
||||
}
|
||||
//odd pixel (D3-D0)
|
||||
if((flags & 0x8) && !(srcdata & 0x0f)) { //FG only and src odd pixel=0
|
||||
if(flags & 0x40) keepmask &= 0xf0; // no odd
|
||||
if ((flags & 0x8) && !(srcdata & 0x0f)) { //FG only and src odd pixel=0
|
||||
if (flags & 0x40) keepmask &= 0xf0; // no odd
|
||||
} else {
|
||||
if(!(flags & 0x40)) keepmask &= 0xf0; // not no odd
|
||||
if (!(flags & 0x40)) keepmask &= 0xf0; // not no odd
|
||||
}
|
||||
curpix &= keepmask;
|
||||
if(flags & 0x10) // solid bit
|
||||
if (flags & 0x10) // solid bit
|
||||
curpix |= (solid & ~keepmask);
|
||||
else
|
||||
curpix |= (srcdata & ~keepmask);
|
||||
|
@ -262,18 +261,18 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
memwrite_williams(dstaddr, curpix);
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*
|
||||
var trace = false;
|
||||
var _traceinsns = {};
|
||||
function _trace() {
|
||||
var pc = cpu.getPC();
|
||||
if (!_traceinsns[pc]) {
|
||||
_traceinsns[pc] = 1;
|
||||
console.log(hex(pc), cpu.getTstates());
|
||||
// TODO
|
||||
/*
|
||||
var trace = false;
|
||||
var _traceinsns = {};
|
||||
function _trace() {
|
||||
var pc = cpu.getPC();
|
||||
if (!_traceinsns[pc]) {
|
||||
_traceinsns[pc] = 1;
|
||||
console.log(hex(pc), cpu.getTstates());
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
this.start = function() {
|
||||
ram = new RAM(0xc000);
|
||||
nvram = new RAM(0x400);
|
||||
|
@ -282,39 +281,39 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
//rom = padBytes(new lzgmini().decode(ROBOTRON_ROM).slice(0), 0xc001);
|
||||
membus = {
|
||||
read: memread_williams,
|
||||
write: memwrite_williams,
|
||||
write: memwrite_williams,
|
||||
};
|
||||
this.readAddress = membus.read;
|
||||
var iobus = {
|
||||
read: function(a) {return 0;},
|
||||
write: function(a,v) {console.log(hex(a),hex(v));}
|
||||
read: function(a) { return 0; },
|
||||
write: function(a, v) { console.log(hex(a), hex(v)); }
|
||||
}
|
||||
cpu = self.newCPU(membus, iobus);
|
||||
|
||||
audio = new MasterAudio();
|
||||
worker = new Worker("./src/audio/z80worker.js");
|
||||
workerchannel = new WorkerSoundChannel(worker);
|
||||
workerchannel = new WorkerSoundChannel(worker);
|
||||
audio.master.addChannel(workerchannel);
|
||||
|
||||
video = new RasterVideo(mainElement, SCREEN_WIDTH, SCREEN_HEIGHT, {rotate:-90});
|
||||
video = new RasterVideo(mainElement, SCREEN_WIDTH, SCREEN_HEIGHT, { rotate: -90 });
|
||||
video.create();
|
||||
$(video.canvas).click(function(e) {
|
||||
var x = Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width());
|
||||
var y = Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height());
|
||||
var addr = (x>>3) + (y*32) + 0x400;
|
||||
if (displayPCs) console.log(x, y, hex(addr,4), "PC", hex(displayPCs[addr],4));
|
||||
});
|
||||
$(video.canvas).click(function(e) {
|
||||
var x = Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width());
|
||||
var y = Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height());
|
||||
var addr = (x >> 3) + (y * 32) + 0x400;
|
||||
if (displayPCs) console.log(x, y, hex(addr, 4), "PC", hex(displayPCs[addr], 4));
|
||||
});
|
||||
var idata = video.getFrameData();
|
||||
setKeyboardFromMap(video, pia6821, ROBOTRON_KEYCODE_MAP);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
|
||||
this.getRasterScanline = function() { return video_counter; }
|
||||
|
||||
this.advance = function(novideo:boolean) {
|
||||
this.advance = function(novideo: boolean) {
|
||||
var cpuCyclesPerSection = Math.round(cpuCyclesPerFrame / 65);
|
||||
for (var sl=0; sl<256; sl+=4) {
|
||||
for (var sl = 0; sl < 256; sl += 4) {
|
||||
video_counter = sl;
|
||||
// interrupts happen every 1/4 of the screen
|
||||
if (sl == 0 || sl == 0x3c || sl == 0xbc || sl == 0xfc) {
|
||||
|
@ -326,12 +325,12 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
}
|
||||
}
|
||||
this.runCPU(cpu, cpuCyclesPerSection);
|
||||
if (sl < 256) video.updateFrame(0, 0, 256-4-sl, 0, 4, 304);
|
||||
if (sl < 256) video.updateFrame(0, 0, 256 - 4 - sl, 0, 4, 304);
|
||||
}
|
||||
// last 6 lines
|
||||
this.runCPU(cpu, cpuCyclesPerSection*2);
|
||||
this.runCPU(cpu, cpuCyclesPerSection * 2);
|
||||
if (screenNeedsRefresh && !novideo) {
|
||||
for (var i=0; i<0x9800; i++)
|
||||
for (var i = 0; i < 0x9800; i++)
|
||||
drawDisplayByte(i, ram.mem[i]);
|
||||
screenNeedsRefresh = false;
|
||||
}
|
||||
|
@ -345,7 +344,7 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
this.loadSoundROM = function(data) {
|
||||
console.log("loading sound ROM " + data.length + " bytes");
|
||||
var soundrom = padBytes(data, 0x4000);
|
||||
worker.postMessage({rom:soundrom});
|
||||
worker.postMessage({ rom: soundrom });
|
||||
}
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
|
@ -374,14 +373,14 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
}
|
||||
this.saveState = function() {
|
||||
return {
|
||||
c:self.getCPUState(),
|
||||
b:ram.mem.slice(0),
|
||||
nvram:nvram.mem.slice(0),
|
||||
pia:pia6821.slice(0),
|
||||
blt:blitregs.slice(0),
|
||||
wdc:watchdog_counter,
|
||||
bs:banksel,
|
||||
ps:portsel,
|
||||
c: self.getCPUState(),
|
||||
b: ram.mem.slice(0),
|
||||
nvram: nvram.mem.slice(0),
|
||||
pia: pia6821.slice(0),
|
||||
blt: blitregs.slice(0),
|
||||
wdc: watchdog_counter,
|
||||
bs: banksel,
|
||||
ps: portsel,
|
||||
};
|
||||
}
|
||||
this.loadControlsState = function(state) {
|
||||
|
@ -389,7 +388,7 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
}
|
||||
this.saveControlsState = function() {
|
||||
return {
|
||||
pia:pia6821.slice(0),
|
||||
pia: pia6821.slice(0),
|
||||
};
|
||||
}
|
||||
this.getCPUState = function() {
|
||||
|
@ -417,6 +416,10 @@ var WilliamsPlatform = function(mainElement, proto) {
|
|||
cpuFrequency *= scale;
|
||||
cpuCyclesPerFrame *= scale;
|
||||
}
|
||||
this.getMemoryMap = function() { return { main:[
|
||||
{name:'Video RAM',start:0x0000,size:0xc000,type:'ram'},
|
||||
{name:'I/O Registers',start:0xc000,size:0x1000,type:'io'},
|
||||
] } };
|
||||
}
|
||||
|
||||
var WilliamsZ80Platform = function(mainElement) {
|
||||
|
@ -433,7 +436,7 @@ var WilliamsZ80Platform = function(mainElement) {
|
|||
var w = blt[6] ^ 4; // blitter bug fix
|
||||
var h = blt[7] ^ 4;
|
||||
return "\nBLIT"
|
||||
+ " " + hex(sstart,4) + " " + hex(dstart,4)
|
||||
+ " " + hex(sstart, 4) + " " + hex(dstart, 4)
|
||||
+ " w:" + hex(w) + " h:" + hex(h)
|
||||
+ " f:" + hex(blt[0]) + " s:" + hex(blt[1]);
|
||||
}
|
||||
|
|
|
@ -298,10 +298,10 @@ export class CodeProject {
|
|||
}
|
||||
}
|
||||
// save and sort segment list
|
||||
this.segments = data.segments;
|
||||
if (this.segments) {
|
||||
this.segments.sort((a,b) => {return a.start-b.start});
|
||||
}
|
||||
var segs = (this.platform.getMemoryMap && this.platform.getMemoryMap()["main"]) || [];
|
||||
segs = segs.concat(data.segments || []);
|
||||
segs.sort((a,b) => {return a.start-b.start});
|
||||
this.segments = segs;
|
||||
}
|
||||
|
||||
getListings() : CodeListingMap {
|
||||
|
|
116
src/recorder.ts
116
src/recorder.ts
|
@ -177,3 +177,119 @@ export class EmuProfilerImpl implements EmuProfiler {
|
|||
this.log(a | PROFOP_INTERRUPT);
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
import { Probeable, ProbeAll } from "./devices";
|
||||
|
||||
export enum ProbeFlags {
|
||||
CLOCKS = 0x00000000,
|
||||
EXECUTE = 0x01000000,
|
||||
MEM_READ = 0x02000000,
|
||||
MEM_WRITE = 0x03000000,
|
||||
IO_READ = 0x04000000,
|
||||
IO_WRITE = 0x05000000,
|
||||
VRAM_READ = 0x06000000,
|
||||
VRAM_WRITE = 0x07000000,
|
||||
INTERRUPT = 0x08000000,
|
||||
ILLEGAL = 0x09000000,
|
||||
SCANLINE = 0x7e000000,
|
||||
FRAME = 0x7f000000,
|
||||
}
|
||||
|
||||
class ProbeFrame {
|
||||
data : Uint32Array;
|
||||
len : number;
|
||||
}
|
||||
|
||||
export class ProbeRecorder implements ProbeAll {
|
||||
|
||||
buf = new Uint32Array(0x100000);
|
||||
idx = 0;
|
||||
fclk = 0;
|
||||
sl = 0;
|
||||
m : Probeable;
|
||||
singleFrame : boolean = true;
|
||||
|
||||
constructor(m:Probeable) {
|
||||
this.m = m;
|
||||
}
|
||||
start() {
|
||||
this.m.connectProbe(this);
|
||||
}
|
||||
stop() {
|
||||
this.m.connectProbe(null);
|
||||
}
|
||||
reset() {
|
||||
this.idx = 0;
|
||||
}
|
||||
log(a:number) {
|
||||
// TODO: coalesce READ and EXECUTE
|
||||
if (this.idx >= this.buf.length) return;
|
||||
this.buf[this.idx++] = a;
|
||||
}
|
||||
relog(a:number) {
|
||||
this.buf[this.idx-1] = a;
|
||||
}
|
||||
lastOp() {
|
||||
if (this.idx > 0)
|
||||
return this.buf[this.idx-1] & 0xff000000;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
lastAddr() {
|
||||
if (this.idx > 0)
|
||||
return this.buf[this.idx-1] & 0xffffff;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
logClocks(clocks:number) {
|
||||
if (clocks > 0) {
|
||||
this.fclk += clocks;
|
||||
if (this.lastOp() == ProbeFlags.CLOCKS)
|
||||
this.relog((this.lastAddr() + clocks) | ProbeFlags.CLOCKS); // coalesce clocks
|
||||
else
|
||||
this.log(clocks | ProbeFlags.CLOCKS);
|
||||
}
|
||||
}
|
||||
logNewScanline() {
|
||||
this.log(ProbeFlags.SCANLINE);
|
||||
this.sl++;
|
||||
}
|
||||
logNewFrame() {
|
||||
this.log(ProbeFlags.FRAME);
|
||||
this.sl = 0;
|
||||
if (this.singleFrame) this.reset();
|
||||
}
|
||||
logExecute(address:number) {
|
||||
this.log(address | ProbeFlags.EXECUTE);
|
||||
}
|
||||
logInterrupt(type:number) {
|
||||
this.log(type | ProbeFlags.INTERRUPT);
|
||||
}
|
||||
logRead(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.MEM_READ);
|
||||
}
|
||||
logWrite(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.MEM_WRITE);
|
||||
}
|
||||
logIORead(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.IO_READ);
|
||||
}
|
||||
logIOWrite(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.IO_WRITE);
|
||||
}
|
||||
logVRAMRead(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.VRAM_READ);
|
||||
}
|
||||
logVRAMWrite(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.VRAM_WRITE);
|
||||
}
|
||||
logIllegal(address:number) {
|
||||
this.log(address | ProbeFlags.ILLEGAL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: handle runToVsync() without erasing entire frame
|
||||
|
||||
|
|
86
src/ui.ts
86
src/ui.ts
|
@ -6,7 +6,7 @@ import * as bootstrap from "bootstrap";
|
|||
import { CodeProject } from "./project";
|
||||
import { WorkerResult, WorkerOutput, VerilogOutput, SourceFile, WorkerError, FileData } from "./workertypes";
|
||||
import { ProjectWindows } from "./windows";
|
||||
import { Platform, Preset, DebugSymbols, DebugEvalCondition } from "./baseplatform";
|
||||
import { Platform, Preset, DebugSymbols, DebugEvalCondition, isDebuggable } from "./baseplatform";
|
||||
import { PLATFORMS, EmuHalt, Toolbar } from "./emu";
|
||||
import * as Views from "./views";
|
||||
import { createNewPersistentStore } from "./store";
|
||||
|
@ -88,6 +88,16 @@ function alertInfo(s:string) {
|
|||
bootbox.alert(s);
|
||||
}
|
||||
|
||||
export function loadScript(scriptfn:string) : Promise<Event> {
|
||||
return new Promise( (resolve, reject) => {
|
||||
var script = document.createElement('script');
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
script.src = scriptfn;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function newWorker() : Worker {
|
||||
return new Worker("./src/worker/loader.js");
|
||||
}
|
||||
|
@ -177,6 +187,7 @@ function refreshWindowList() {
|
|||
var a = document.createElement("a");
|
||||
a.setAttribute("class", "dropdown-item");
|
||||
a.setAttribute("href", "#");
|
||||
a.setAttribute("data-wndid", id);
|
||||
if (id == projectWindows.getActiveID())
|
||||
$(a).addClass("dropdown-item-checked");
|
||||
a.appendChild(document.createTextNode(name));
|
||||
|
@ -243,17 +254,26 @@ function refreshWindowList() {
|
|||
return new Views.MemoryView();
|
||||
});
|
||||
}
|
||||
if (current_project.segments && current_project.segments.length) {
|
||||
addWindowItem("#memmap", "Memory Map", () => {
|
||||
return new Views.MemoryMapView();
|
||||
});
|
||||
}
|
||||
if (platform.readVRAMAddress) {
|
||||
addWindowItem("#memvram", "VRAM Browser", () => {
|
||||
return new Views.VRAMMemoryView();
|
||||
});
|
||||
}
|
||||
if (current_project.segments) {
|
||||
addWindowItem("#memmap", "Memory Map", () => {
|
||||
return new Views.MemoryMapView();
|
||||
if (platform.startProbing) {
|
||||
addWindowItem("#memheatmap", "Memory Probe", () => {
|
||||
return new Views.AddressHeatMapView();
|
||||
});
|
||||
// TODO: only if raster
|
||||
addWindowItem("#crtheatmap", "CRT Probe", () => {
|
||||
return new Views.RasterPCHeatMapView();
|
||||
});
|
||||
}
|
||||
if (platform.getRasterScanline && platform.setBreakpoint && platform.getCPUState) { // TODO: use profiler class to determine compat
|
||||
else if (platform.getRasterScanline && platform.setBreakpoint && platform.getCPUState) { // TODO: use profiler class to determine compat
|
||||
addWindowItem("#profiler", "Profiler", () => {
|
||||
return new Views.ProfileView();
|
||||
});
|
||||
|
@ -284,11 +304,15 @@ function loadProject(preset_id:string) {
|
|||
// file found; continue
|
||||
loadMainWindow(preset_id);
|
||||
} else {
|
||||
// no file data, load skeleton file
|
||||
getSkeletonFile(preset_id).then((skel) => {
|
||||
current_project.filedata[preset_id] = skel || "\n";
|
||||
loadMainWindow(preset_id);
|
||||
//alertInfo("No existing file found; loading default file");
|
||||
// don't alert if we selected "new file"
|
||||
if (!qs['newfile']) {
|
||||
alertInfo("Could not find file \"" + preset_id + "\". Loading default file.");
|
||||
}
|
||||
delete qs['newfile'];
|
||||
replaceURLState();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -338,6 +362,7 @@ function _createNewFile(e) {
|
|||
}
|
||||
var path = filename;
|
||||
gaEvent('workspace', 'file', 'new');
|
||||
qs['newfile'] = '1';
|
||||
reloadProject(path);
|
||||
}
|
||||
}
|
||||
|
@ -497,8 +522,8 @@ function _importProjectFromGithub(e) {
|
|||
|
||||
function _publishProjectToGithub(e) {
|
||||
if (repo_id) {
|
||||
alertError("This project (" + current_project.mainPath + ") is already bound to a Github repository. Choose 'Push Changes' to update.");
|
||||
return;
|
||||
if (!confirm("This project (" + current_project.mainPath + ") is already bound to a Github repository. Do you want to re-publish to a new repository? (You can instead choose 'Push Changes' to update files in the existing repository.)"))
|
||||
return;
|
||||
}
|
||||
var modal = $("#publishGithubModal");
|
||||
var btn = $("#publishGithubButton");
|
||||
|
@ -671,7 +696,7 @@ function _shareEmbedLink(e) {
|
|||
return true;
|
||||
}
|
||||
loadClipboardLibrary();
|
||||
loadScript('lib/liblzg.js', () => {
|
||||
loadScript('lib/liblzg.js').then( () => {
|
||||
// TODO: Module is bad var name (conflicts with MAME)
|
||||
var lzgrom = compressLZG( window['Module'], Array.from(<Uint8Array>current_output) );
|
||||
window['Module'] = null; // so we load it again next time
|
||||
|
@ -696,7 +721,7 @@ function _shareEmbedLink(e) {
|
|||
}
|
||||
|
||||
function loadClipboardLibrary() {
|
||||
loadScript('lib/clipboard.min.js', () => {
|
||||
loadScript('lib/clipboard.min.js').then( () => {
|
||||
var ClipboardJS = exports['ClipboardJS'];
|
||||
new ClipboardJS(".btn");
|
||||
});
|
||||
|
@ -721,7 +746,7 @@ function _downloadCassetteFile(e) {
|
|||
alertError("Cassette export is not supported on this platform.");
|
||||
return true;
|
||||
}
|
||||
loadScript('lib/c2t.js', () => {
|
||||
loadScript('lib/c2t.js').then( () => {
|
||||
var stdout = '';
|
||||
var print_fn = function(s) { stdout += s + "\n"; }
|
||||
var c2t = window['c2t']({
|
||||
|
@ -838,7 +863,7 @@ function _downloadSourceFile(e) {
|
|||
}
|
||||
|
||||
function _downloadProjectZipFile(e) {
|
||||
loadScript('lib/jszip.min.js', () => {
|
||||
loadScript('lib/jszip.min.js').then( () => {
|
||||
var zip = new JSZip();
|
||||
current_project.iterateFiles( (id, data) => {
|
||||
if (data) {
|
||||
|
@ -852,7 +877,7 @@ function _downloadProjectZipFile(e) {
|
|||
}
|
||||
|
||||
function _downloadAllFilesZipFile(e) {
|
||||
loadScript('lib/jszip.min.js', () => {
|
||||
loadScript('lib/jszip.min.js').then( () => {
|
||||
var zip = new JSZip();
|
||||
store.keys( (err, keys : string[]) => {
|
||||
return Promise.all(keys.map( (path) => {
|
||||
|
@ -1020,11 +1045,12 @@ function loadBIOSFromProject() {
|
|||
}
|
||||
|
||||
function showDebugInfo(state?) {
|
||||
if (!isDebuggable(platform)) return;
|
||||
var meminfo = $("#mem_info");
|
||||
var allcats = platform.getDebugCategories && platform.getDebugCategories();
|
||||
var allcats = platform.getDebugCategories();
|
||||
if (allcats && !debugCategory)
|
||||
debugCategory = allcats[0];
|
||||
var s = state && platform.getDebugInfo && platform.getDebugInfo(debugCategory, state);
|
||||
var s = state && platform.getDebugInfo(debugCategory, state);
|
||||
if (s) {
|
||||
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
|
||||
meminfo.show().html(hs);
|
||||
|
@ -1250,7 +1276,7 @@ function updateDebugWindows() {
|
|||
projectWindows.tick();
|
||||
debugTickPaused = true;
|
||||
}
|
||||
setTimeout(updateDebugWindows, 200);
|
||||
setTimeout(updateDebugWindows, 100);
|
||||
}
|
||||
|
||||
function setWaitDialog(b : boolean) {
|
||||
|
@ -1270,7 +1296,7 @@ function setWaitProgress(prog : number) {
|
|||
var recordingVideo = false;
|
||||
function _recordVideo() {
|
||||
if (recordingVideo) return;
|
||||
loadScript("gif.js/dist/gif.js", () => {
|
||||
loadScript("gif.js/dist/gif.js").then( () => {
|
||||
var canvas = $("#emulator").find("canvas")[0] as HTMLElement;
|
||||
if (!canvas) {
|
||||
alertError("Could not find canvas element to record video!");
|
||||
|
@ -1678,7 +1704,7 @@ function installErrorHandler() {
|
|||
var msgstr = msgevent+"";
|
||||
console.log(msgevent, url, line, col, error);
|
||||
// emulation threw EmuHalt
|
||||
if (error instanceof EmuHalt || msgstr.indexOf("CPU STOP") >= 0) {
|
||||
if (error instanceof EmuHalt || msgstr.indexOf("CPU STOP") >= 0) { // TODO
|
||||
showErrorAlert([ {msg:msgstr, line:0} ]);
|
||||
uiDebugCallback(platform.saveState && platform.saveState());
|
||||
setDebugButtonState("pause", "stopped");
|
||||
|
@ -1831,14 +1857,6 @@ function revealTopBar() {
|
|||
setTimeout(() => { $("#controls_dynamic").css('visibility','inherit'); }, 250);
|
||||
}
|
||||
|
||||
export function loadScript(scriptfn, onload, onerror?) {
|
||||
var script = document.createElement('script');
|
||||
script.onload = onload;
|
||||
script.onerror = onerror;
|
||||
script.src = scriptfn;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}
|
||||
|
||||
export function setupSplits() {
|
||||
const splitName = 'workspace-split3-' + platform_id;
|
||||
var sizes = [0, 50, 50];
|
||||
|
@ -1895,7 +1913,7 @@ function loadImportedURL(url : string) {
|
|||
}
|
||||
|
||||
function setPlatformUI() {
|
||||
var name = platform.getMetadata && platform.getMetadata().name;
|
||||
var name = platform.getPlatformName && platform.getPlatformName();
|
||||
var menuitem = $('a[href="?platform='+platform_id+'"]');
|
||||
if (menuitem.length) {
|
||||
menuitem.addClass("dropdown-item-checked");
|
||||
|
@ -1954,9 +1972,12 @@ export function startUI(loadplatform : boolean) {
|
|||
}
|
||||
|
||||
function loadAndStartPlatform() {
|
||||
var scriptfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js';
|
||||
loadScript(scriptfn, () => {
|
||||
console.log("starting platform", platform_id);
|
||||
var platformfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js'; // required file
|
||||
var machinefn = platformfn.replace('/platform/', '/machine/'); // optional file
|
||||
loadScript(platformfn).then( () => {
|
||||
return loadScript(machinefn).catch(() => { console.log('skipped',machinefn); }); // optional file skipped
|
||||
}).then( () => {
|
||||
console.log("starting platform", platform_id); // loaded required <platform_id>.js file
|
||||
try {
|
||||
startPlatform();
|
||||
showWelcomeMessage();
|
||||
|
@ -1964,7 +1985,8 @@ function loadAndStartPlatform() {
|
|||
} finally {
|
||||
revealTopBar();
|
||||
}
|
||||
}, () => {
|
||||
}).catch( (e) => {
|
||||
console.log(e);
|
||||
alertError('Platform "' + platform_id + '" not supported.');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
'use strict';
|
||||
/*
|
||||
* js99'er - TI-99/4A emulator written in JavaScript
|
||||
*
|
||||
|
@ -11,6 +10,7 @@
|
|||
*/
|
||||
|
||||
import { hex, lpad, RGBA } from "../util";
|
||||
import { ProbeVRAM, NullProbe } from "../devices";
|
||||
|
||||
enum TMS9918A_Mode {
|
||||
GRAPHICS = 0,
|
||||
|
@ -31,6 +31,7 @@ export class TMS9918A {
|
|||
|
||||
cru : { setVDPInterrupt: (b:boolean) => void };
|
||||
enableFlicker : boolean;
|
||||
probe : ProbeVRAM = new NullProbe();
|
||||
|
||||
ram = new Uint8Array(16384); // VDP RAM
|
||||
registers = new Uint8Array(8);
|
||||
|
@ -492,6 +493,7 @@ export class TMS9918A {
|
|||
}
|
||||
|
||||
writeData(i:number) {
|
||||
this.probe.logVRAMWrite(this.addressRegister, i);
|
||||
this.ram[this.addressRegister++] = i;
|
||||
this.prefetchByte = i;
|
||||
this.addressRegister &= this.ramMask;
|
||||
|
@ -512,6 +514,7 @@ export class TMS9918A {
|
|||
readData() : number {
|
||||
var i = this.prefetchByte;
|
||||
this.prefetchByte = this.ram[this.addressRegister++];
|
||||
this.probe.logVRAMRead(this.addressRegister-1, this.prefetchByte);
|
||||
this.addressRegister &= this.ramMask;
|
||||
this.latch = false;
|
||||
return i;
|
||||
|
|
266
src/views.ts
266
src/views.ts
|
@ -1,13 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
import $ = require("jquery");
|
||||
//import CodeMirror = require("codemirror");
|
||||
import { SourceFile, WorkerError, Segment, FileData } from "./workertypes";
|
||||
import { Platform, EmuState, ProfilerOutput, lookupSymbol, BaseDebugPlatform } from "./baseplatform";
|
||||
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
|
||||
import { EmuProfilerImpl } from "./recorder";
|
||||
import { EmuProfilerImpl, ProbeRecorder, ProbeFlags } from "./recorder";
|
||||
import { getMousePos } from "./emu";
|
||||
import * as pixed from "./pixed/pixeleditor";
|
||||
declare var Mousetrap;
|
||||
|
||||
|
@ -536,6 +535,7 @@ export class MemoryView implements ProjectView {
|
|||
maindiv : HTMLElement;
|
||||
static IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
|
||||
recreateOnResize = true;
|
||||
totalRows = 0x1400;
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
var div = document.createElement('div');
|
||||
|
@ -550,7 +550,7 @@ export class MemoryView implements ProjectView {
|
|||
w: $(workspace).width(),
|
||||
h: $(workspace).height(),
|
||||
itemHeight: getVisibleEditorLineHeight(),
|
||||
totalRows: 0x1400,
|
||||
totalRows: this.totalRows,
|
||||
generatorFn: (row : number) => {
|
||||
var s = this.getMemoryLineAt(row);
|
||||
var linediv = document.createElement("div");
|
||||
|
@ -697,6 +697,7 @@ export class MemoryView implements ProjectView {
|
|||
}
|
||||
|
||||
export class VRAMMemoryView extends MemoryView {
|
||||
totalRows = 0x800;
|
||||
readAddress(n : number) {
|
||||
return platform.readVRAMAddress(n);
|
||||
}
|
||||
|
@ -924,6 +925,263 @@ export class ProfileView implements ProjectView {
|
|||
|
||||
///
|
||||
|
||||
// TODO: clear buffer when scrubbing
|
||||
|
||||
abstract class ProbeViewBase {
|
||||
|
||||
probe : ProbeRecorder;
|
||||
maindiv : HTMLElement;
|
||||
canvas : HTMLCanvasElement;
|
||||
ctx : CanvasRenderingContext2D;
|
||||
tooldiv : HTMLElement;
|
||||
recreateOnResize = true;
|
||||
|
||||
createCanvas(parent:HTMLElement, width:number, height:number) {
|
||||
var div = document.createElement('div');
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.classList.add('pixelated');
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '90vh'; // i hate css
|
||||
canvas.style.backgroundColor = 'black';
|
||||
canvas.style.cursor = 'crosshair';
|
||||
canvas.onmousemove = (e) => {
|
||||
var pos = getMousePos(canvas, e);
|
||||
this.showTooltip(this.getTooltipText(pos.x, pos.y));
|
||||
$(this.tooldiv).css('left',e.pageX+10).css('top',e.pageY-30);
|
||||
}
|
||||
canvas.onmouseout = (e) => {
|
||||
$(this.tooldiv).hide();
|
||||
}
|
||||
parent.appendChild(div);
|
||||
div.appendChild(canvas);
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.initCanvas();
|
||||
return this.maindiv = div;
|
||||
}
|
||||
|
||||
addr2str(addr : number) : string {
|
||||
var _addr2sym = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {};
|
||||
var sym = _addr2sym[addr];
|
||||
if (typeof sym === 'string')
|
||||
return '$' + hex(addr) + ' (' + sym + ')';
|
||||
else
|
||||
return '$' + hex(addr);
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
}
|
||||
|
||||
showTooltip(s:string) {
|
||||
if (s) {
|
||||
if (!this.tooldiv) {
|
||||
this.tooldiv = document.createElement("div");
|
||||
this.tooldiv.setAttribute("class", "tooltiptrack");
|
||||
document.body.appendChild(this.tooldiv);
|
||||
}
|
||||
$(this.tooldiv).text(s).show();
|
||||
} else {
|
||||
$(this.tooldiv).hide();
|
||||
}
|
||||
}
|
||||
|
||||
getTooltipText(x:number, y:number) : string {
|
||||
return null;
|
||||
}
|
||||
|
||||
setVisible(showing : boolean) : void {
|
||||
if (showing) {
|
||||
this.probe = platform.startProbing();
|
||||
this.tick();
|
||||
} else {
|
||||
platform.stopProbing();
|
||||
this.probe = null;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
}
|
||||
|
||||
redraw( eventfn:(op,addr,col,row) => void ) {
|
||||
var p = this.probe;
|
||||
if (!p || !p.idx) return; // if no probe, or if empty
|
||||
var row=0;
|
||||
var col=0;
|
||||
for (var i=0; i<p.idx; i++) {
|
||||
var word = p.buf[i];
|
||||
var addr = word & 0xffffff;
|
||||
var op = word & 0xff000000;
|
||||
switch (op) {
|
||||
case ProbeFlags.SCANLINE: row++; col=0; break;
|
||||
case ProbeFlags.FRAME: row=0; col=0; break;
|
||||
case ProbeFlags.CLOCKS: col += addr; break;
|
||||
default:
|
||||
eventfn(op, addr, col, row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.clear();
|
||||
this.redraw(this.drawEvent.bind(this));
|
||||
}
|
||||
|
||||
abstract drawEvent(op, addr, col, row);
|
||||
}
|
||||
|
||||
abstract class ProbeBitmapViewBase extends ProbeViewBase {
|
||||
|
||||
imageData : ImageData;
|
||||
datau32 : Uint32Array;
|
||||
recreateOnResize = false;
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
var width = 160;
|
||||
var height = 262;
|
||||
try {
|
||||
width = Math.ceil(platform['machine']['cpuCyclesPerLine']) || 256; // TODO
|
||||
height = Math.ceil(platform['machine']['numTotalScanlines']) || 262; // TODO
|
||||
} catch (e) {
|
||||
}
|
||||
return this.createCanvas(parent, width, height);
|
||||
}
|
||||
initCanvas() {
|
||||
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
|
||||
this.datau32 = new Uint32Array(this.imageData.data.buffer);
|
||||
}
|
||||
getTooltipText(x:number, y:number) : string {
|
||||
x = x|0;
|
||||
y = y|0;
|
||||
var s = "";
|
||||
this.redraw( (op,addr,col,row) => {
|
||||
if (y == row && x == col) {
|
||||
s += "\n" + this.opToString(op, addr);
|
||||
}
|
||||
} );
|
||||
return 'X: ' + x + ' Y: ' + y + ' ' + s;
|
||||
}
|
||||
opToString(op:number, addr?:number) {
|
||||
var s = "";
|
||||
switch (op) {
|
||||
case ProbeFlags.EXECUTE: s = "Exec"; break;
|
||||
case ProbeFlags.MEM_READ: s = "Read"; break;
|
||||
case ProbeFlags.MEM_WRITE: s = "Write"; break;
|
||||
case ProbeFlags.IO_READ: s = "IO Read"; break;
|
||||
case ProbeFlags.IO_WRITE: s = "IO Write"; break;
|
||||
case ProbeFlags.VRAM_READ: s = "VRAM Read"; break;
|
||||
case ProbeFlags.VRAM_WRITE: s = "VRAM Write"; break;
|
||||
case ProbeFlags.INTERRUPT: s = "Interrupt"; break;
|
||||
case ProbeFlags.ILLEGAL: s = "Error"; break;
|
||||
default: s = ""; break;
|
||||
}
|
||||
return typeof addr == 'number' ? s + " " + this.addr2str(addr) : s;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.tick();
|
||||
this.datau32.fill(0xff000000);
|
||||
}
|
||||
tick() {
|
||||
super.tick();
|
||||
this.ctx.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
clear() {
|
||||
this.datau32.fill(0xff000000);
|
||||
}
|
||||
getOpRGB(op:number) : number {
|
||||
switch (op) {
|
||||
case ProbeFlags.EXECUTE: return 0x018001;
|
||||
case ProbeFlags.MEM_READ: return 0x800101;
|
||||
case ProbeFlags.MEM_WRITE: return 0x010180;
|
||||
case ProbeFlags.IO_READ: return 0x018080;
|
||||
case ProbeFlags.IO_WRITE: return 0xc00180;
|
||||
case ProbeFlags.VRAM_READ: return 0x808001;
|
||||
case ProbeFlags.VRAM_WRITE: return 0x4080c0;
|
||||
case ProbeFlags.INTERRUPT: return 0xcfcfcf;
|
||||
case ProbeFlags.ILLEGAL: return 0x3f3fff;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectView {
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
return this.createCanvas(parent, 256, 256);
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (var i=0; i<=0xffff; i++) {
|
||||
var v = platform.readAddress(i);
|
||||
var rgb = (v >> 2) | (v & 0x1f);
|
||||
rgb |= (rgb<<8) | (rgb<<16);
|
||||
this.datau32[i] = rgb | 0xff000000;
|
||||
}
|
||||
}
|
||||
|
||||
drawEvent(op, addr, col, row) {
|
||||
var rgb = this.getOpRGB(op);
|
||||
if (!rgb) return;
|
||||
var x = addr & 0xff;
|
||||
var y = (addr >> 8) & 0xff;
|
||||
var data = this.datau32[addr & 0xffff];
|
||||
data = data | rgb | 0xff000000;
|
||||
this.datau32[addr & 0xffff] = data;
|
||||
}
|
||||
|
||||
getTooltipText(x:number, y:number) : string {
|
||||
var a = (x & 0xff) + (y << 8);
|
||||
var s = this.addr2str(a);
|
||||
var pc = -1;
|
||||
var already = {};
|
||||
this.redraw( (op,addr,col,row) => {
|
||||
if (op == ProbeFlags.EXECUTE) {
|
||||
pc = addr;
|
||||
}
|
||||
var key = op|pc;
|
||||
if (addr == a && !already[key]) {
|
||||
s += "\nPC " + this.addr2str(pc) + " " + this.opToString(op);
|
||||
already[key] = 1;
|
||||
}
|
||||
} );
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class RasterHeatMapView extends ProbeBitmapViewBase implements ProjectView {
|
||||
|
||||
drawEvent(op, addr, col, row) {
|
||||
if (op == ProbeFlags.EXECUTE || op == ProbeFlags.MEM_READ) return;
|
||||
var rgb = this.getOpRGB(op);
|
||||
if (!rgb) return;
|
||||
var iofs = col + row * this.canvas.width;
|
||||
var data = this.datau32[iofs];
|
||||
data = data | rgb | 0xff000000;
|
||||
this.datau32[iofs] = data;
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
export class RasterPCHeatMapView extends ProbeBitmapViewBase implements ProjectView {
|
||||
|
||||
drawEvent(op, addr, col, row) {
|
||||
var iofs = col + row * this.canvas.width;
|
||||
var rgb = this.getOpRGB(op);
|
||||
if (!rgb) return;
|
||||
var data = this.datau32[iofs];
|
||||
data = data | rgb | 0xff000000;
|
||||
this.datau32[iofs] = data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
maindiv : JQuery;
|
||||
cureditordiv : JQuery;
|
||||
|
|
|
@ -70,7 +70,7 @@ function VirtualList(config) {
|
|||
function onScroll(e) {
|
||||
var scrollTop = e.target.scrollTop; // Triggers reflow
|
||||
if (!lastRepaintY || Math.abs(scrollTop - lastRepaintY) > maxBuffer) {
|
||||
var first = parseInt(scrollTop / itemHeight) - screenItemsLen;
|
||||
var first = Math.floor(scrollTop / itemHeight) - screenItemsLen;
|
||||
self._renderChunk(self.container, first < 0 ? 0 : first);
|
||||
lastRepaintY = scrollTop;
|
||||
}
|
||||
|
@ -141,17 +141,17 @@ VirtualList.createContainer = function(w, h) {
|
|||
c.style.height = h;
|
||||
c.style.overflow = 'auto';
|
||||
c.style.position = 'relative';
|
||||
c.style.padding = 0;
|
||||
c.style.padding = '0';
|
||||
c.style.border = '1px solid black';
|
||||
return c;
|
||||
};
|
||||
|
||||
VirtualList.createScroller = function(h) {
|
||||
var scroller = document.createElement('div');
|
||||
scroller.style.opacity = 0;
|
||||
scroller.style.opacity = '0';
|
||||
scroller.style.position = 'absolute';
|
||||
scroller.style.top = 0;
|
||||
scroller.style.left = 0;
|
||||
scroller.style.top = '0';
|
||||
scroller.style.left = '0';
|
||||
scroller.style.width = '1px';
|
||||
scroller.style.height = h + 'px';
|
||||
return scroller;
|
|
@ -9,15 +9,24 @@ MEMORY {
|
|||
HEADER: file = %O, start = $0000, size = $0080, fill = yes;
|
||||
|
||||
# ROM Bank
|
||||
PRG: file = %O, start = $4000, size = $BFFA, fill = yes, define = yes;
|
||||
PRG: file = %O, start = $4000, size = $4000, fill = yes, define = yes;
|
||||
|
||||
# DMA/Code Banks (interleaved for 4K Holey DMA)
|
||||
CHR0: file = %O, start = $8000, size = $1000, fill = yes, define = yes;
|
||||
PRG0: file = %O, start = $9000, size = $1000, fill = yes, define = yes;
|
||||
CHR1: file = %O, start = $A000, size = $1000, fill = yes, define = yes;
|
||||
PRG1: file = %O, start = $B000, size = $1000, fill = yes, define = yes;
|
||||
CHR2: file = %O, start = $C000, size = $1000, fill = yes, define = yes;
|
||||
PRG2: file = %O, start = $D000, size = $1000, fill = yes, define = yes;
|
||||
CHR3: file = %O, start = $E000, size = $1000, fill = yes, define = yes;
|
||||
PRG3: file = %O, start = $F000, size = $0FFA, fill = yes, define = yes;
|
||||
|
||||
# CPU Vectors
|
||||
VECTORS: file = %O, start = $FFFA, size = $0006, fill = yes;
|
||||
|
||||
# standard 2k SRAM (-zeropage)
|
||||
# $0140-$0200 cpu stack
|
||||
# $0500-$0800 3 pages for cc65 parameter stack
|
||||
RAM: file = "", start = $2200, size = __STACKSIZE__, define = yes;
|
||||
RAM0: file = "", start = $1800, size = $840, define = yes;
|
||||
RAM1: file = "", start = $2200, size = __STACKSIZE__, define = yes;
|
||||
}
|
||||
SEGMENTS {
|
||||
ZEROPAGE: load = ZP, type = zp;
|
||||
|
@ -26,9 +35,18 @@ SEGMENTS {
|
|||
RODATA: load = PRG, type = ro, define = yes;
|
||||
ONCE: load = PRG, type = ro, optional = yes;
|
||||
CODE: load = PRG, type = ro, define = yes;
|
||||
DATA: load = PRG, run = RAM, type = rw, define = yes;
|
||||
DATA: load = PRG, run = RAM0, type = rw, define = yes;
|
||||
CHR0: load = CHR0, type = ro, optional = yes;
|
||||
PRG0: load = PRG0, type = ro, optional = yes;
|
||||
CHR1: load = CHR1, type = ro, optional = yes;
|
||||
PRG1: load = PRG1, type = ro, optional = yes;
|
||||
CHR2: load = CHR2, type = ro, optional = yes;
|
||||
PRG2: load = PRG2, type = ro, optional = yes;
|
||||
CHR3: load = CHR3, type = ro, optional = yes;
|
||||
PRG3: load = PRG3, type = ro, optional = yes;
|
||||
VECTORS: load = VECTORS, type = ro;
|
||||
BSS: load = RAM, type = bss, define = yes;
|
||||
BSS: load = RAM0, type = bss, define = yes;
|
||||
RAM1: load = RAM1, type = rw, optional = yes;
|
||||
}
|
||||
FEATURES {
|
||||
CONDES: type = constructor,
|
||||
|
|
Binary file not shown.
|
@ -6,9 +6,11 @@
|
|||
.export _HandyRTI
|
||||
.export NMI,IRQ,START
|
||||
.import initlib,push0,popa,popax,_main,zerobss,copydata
|
||||
.importzp sp
|
||||
|
||||
; Linker generated symbols
|
||||
.import __RAM_START__ ,__RAM_SIZE__
|
||||
.import __RAM0_START__ ,__RAM0_SIZE__
|
||||
.import __RAM1_START__ ,__RAM1_SIZE__
|
||||
.import __ROM0_START__ ,__ROM0_SIZE__
|
||||
.import __STARTUP_LOAD__,__STARTUP_RUN__,__STARTUP_SIZE__
|
||||
.import __CODE_LOAD__ ,__CODE_RUN__ ,__CODE_SIZE__
|
||||
|
@ -82,6 +84,15 @@ _exit:
|
|||
cpx #$40
|
||||
bne @3
|
||||
|
||||
; copy data segment
|
||||
jsr copydata
|
||||
; initialize cc65 stack
|
||||
lda #<(__RAM1_START__+__RAM1_SIZE__)
|
||||
sta sp
|
||||
lda #>(__RAM1_START__+__RAM1_SIZE__)
|
||||
sta sp+1
|
||||
; init CC65 library
|
||||
jsr initlib
|
||||
; set interrupt vector in ZP
|
||||
lda #<_HandyRTI
|
||||
sta INTVEC
|
||||
|
|
|
@ -57,12 +57,6 @@ var PLATFORM_PARAMS = {
|
|||
code_size: 0xf000,
|
||||
data_start: 0x80,
|
||||
data_size: 0x80,
|
||||
extra_segments:[
|
||||
{name:'TIA Registers',start:0x00,size:0x80,type:'io'},
|
||||
{name:'PIA RAM',start:0x80,size:0x80,type:'ram'},
|
||||
{name:'PIA Ports and Timer',start:0x280,size:0x18,type:'io'},
|
||||
{name:'Cartridge ROM',start:0xf000,size:0x1000,type:'rom'},
|
||||
],
|
||||
},
|
||||
'mw8080bw': {
|
||||
code_start: 0x0,
|
||||
|
@ -70,9 +64,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0x2000,
|
||||
data_size: 0x400,
|
||||
stack_end: 0x2400,
|
||||
extra_segments:[
|
||||
{name:'Frame Buffer',start:0x2400,size:7168,type:'ram'},
|
||||
],
|
||||
},
|
||||
'vicdual': {
|
||||
code_start: 0x0,
|
||||
|
@ -80,10 +71,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0xe400,
|
||||
data_size: 0x400,
|
||||
stack_end: 0xe800,
|
||||
extra_segments:[
|
||||
{name:'Cell RAM',start:0xe000,size:32*32,type:'ram'},
|
||||
{name:'Tile RAM',start:0xe800,size:256*8,type:'ram'},
|
||||
],
|
||||
},
|
||||
'galaxian': {
|
||||
code_start: 0x0,
|
||||
|
@ -98,11 +85,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0x4000,
|
||||
data_size: 0x400,
|
||||
stack_end: 0x4800,
|
||||
extra_segments:[
|
||||
{name:'Video RAM',start:0x5000,size:0x400,type:'ram'},
|
||||
{name:'Sprite RAM',start:0x5800,size:0x100,type:'ram'},
|
||||
{name:'I/O Registers',start:0x6000,size:0x2000,type:'io'},
|
||||
],
|
||||
},
|
||||
'williams': {
|
||||
code_start: 0x0,
|
||||
|
@ -110,10 +92,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0x9800,
|
||||
data_size: 0x2800,
|
||||
stack_end: 0xc000,
|
||||
extra_segments:[
|
||||
{name:'Video RAM',start:0x0000,size:0xc000,type:'ram'},
|
||||
{name:'I/O Registers',start:0xc000,size:0x1000,type:'io'},
|
||||
],
|
||||
},
|
||||
'williams-z80': {
|
||||
code_start: 0x0,
|
||||
|
@ -121,10 +99,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0x9800,
|
||||
data_size: 0x2800,
|
||||
stack_end: 0xc000,
|
||||
extra_segments:[
|
||||
{name:'Video RAM',start:0x0000,size:0xc000,type:'ram'},
|
||||
{name:'I/O Registers',start:0xc000,size:0x1000,type:'io'},
|
||||
],
|
||||
},
|
||||
'vector-z80color': {
|
||||
code_start: 0x0,
|
||||
|
@ -132,23 +106,12 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0xe000,
|
||||
data_size: 0x2000,
|
||||
stack_end: 0x0,
|
||||
extra_segments:[
|
||||
{name:'Switches/POKEY I/O',start:0x8000,size:0x100,type:'io'},
|
||||
{name:'Math Box I/O',start:0x8100,size:0x100,type:'io'},
|
||||
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
||||
{name:'DVG RAM',start:0xa000,size:0x4000,type:'ram'},
|
||||
],
|
||||
},
|
||||
'vector-ataricolor': { //TODO
|
||||
define: '__VECTOR__',
|
||||
cfgfile: 'vector-color.cfg',
|
||||
libargs: ['crt0.o', 'sim6502.lib'],
|
||||
extra_link_files: ['crt0.o', 'vector-color.cfg'],
|
||||
extra_segments:[
|
||||
{name:'Switches/POKEY I/O',start:0x7800,size:0x1000,type:'io'},
|
||||
{name:'DVG I/O',start:0x8800,size:0x100,type:'io'},
|
||||
{name:'EAROM',start:0x8900,size:0x100,type:'ram'},
|
||||
],
|
||||
},
|
||||
'sound_williams-z80': {
|
||||
code_start: 0x0,
|
||||
|
@ -173,10 +136,6 @@ var PLATFORM_PARAMS = {
|
|||
stack_end: 0x8000,
|
||||
extra_preproc_args: ['-I', '/share/include/coleco', '-D', 'CV_CV'],
|
||||
extra_link_args: ['-k', '/share/lib/coleco', '-l', 'libcv', '-l', 'libcvu', 'crt0.rel'],
|
||||
extra_segments:[
|
||||
{name:'BIOS',start:0x0,size:0x2000,type:'rom'},
|
||||
{name:'Cartridge Header',start:0x8000,size:0x100,type:'rom'},
|
||||
],
|
||||
},
|
||||
'msx': {
|
||||
rom_start: 0x4000,
|
||||
|
@ -187,13 +146,6 @@ var PLATFORM_PARAMS = {
|
|||
stack_end: 0xffff,
|
||||
extra_link_args: ['crt0-msx.rel'],
|
||||
extra_link_files: ['crt0-msx.rel', 'crt0-msx.lst'],
|
||||
extra_segments:[
|
||||
{name:'BIOS',start:0x0,size:0x4000,type:'rom'},
|
||||
//{name:'Cartridge',start:0x4000,size:0x4000,type:'rom'},
|
||||
{name:'RAM',start:0xc000,size:0x3200,type:'ram'},
|
||||
{name:'Stack',start:0xf000,size:0x300,type:'ram'},
|
||||
{name:'BIOS Work RAM',start:0xf300,size:0xd00},
|
||||
],
|
||||
},
|
||||
'msx-libcv': {
|
||||
rom_start: 0x4000,
|
||||
|
@ -206,13 +158,6 @@ var PLATFORM_PARAMS = {
|
|||
extra_link_args: ['-k', '.', '-l', 'libcv-msx', '-l', 'libcvu-msx', 'crt0-msx.rel'],
|
||||
extra_link_files: ['libcv-msx.lib', 'libcvu-msx.lib', 'crt0-msx.rel', 'crt0-msx.lst'],
|
||||
extra_compile_files: ['cv.h','cv_graphics.h','cv_input.h','cv_sound.h','cv_support.h','cvu.h','cvu_c.h','cvu_compression.h','cvu_f.h','cvu_graphics.h','cvu_input.h','cvu_sound.h'],
|
||||
extra_segments:[
|
||||
{name:'BIOS',start:0x0,size:0x4000,type:'rom'},
|
||||
//{name:'Cartridge',start:0x4000,size:0x4000,type:'rom'},
|
||||
{name:'RAM',start:0xc000,size:0x3200,type:'ram'},
|
||||
{name:'Stack',start:0xf000,size:0x300,type:'ram'},
|
||||
{name:'BIOS Work RAM',start:0xf300,size:0xd00},
|
||||
],
|
||||
},
|
||||
'sms-sg1000-libcv': {
|
||||
rom_start: 0x0000,
|
||||
|
@ -236,13 +181,6 @@ var PLATFORM_PARAMS = {
|
|||
'-D', 'NES_MIRRORING=0', // horizontal mirroring
|
||||
],
|
||||
extra_link_files: ['crt0.o', 'neslib2.lib', 'neslib2.cfg', 'nesbanked.cfg'],
|
||||
extra_segments:[
|
||||
//{name:'Work RAM',start:0x0,size:0x800,type:'ram'},
|
||||
{name:'OAM Buffer',start:0x200,size:0x100,type:'ram'},
|
||||
{name:'PPU Registers',start:0x2000,last:0x2008,size:0x2000,type:'io'},
|
||||
{name:'APU Registers',start:0x4000,last:0x4020,size:0x2000,type:'io'},
|
||||
{name:'Cartridge RAM',start:0x6000,size:0x2000,type:'ram'},
|
||||
],
|
||||
},
|
||||
'apple2': {
|
||||
define: '__APPLE2__',
|
||||
|
@ -250,10 +188,6 @@ var PLATFORM_PARAMS = {
|
|||
libargs: ['apple2.lib'],
|
||||
__CODE_RUN__: 16384,
|
||||
code_start: 0x803,
|
||||
extra_segments:[
|
||||
{name:'I/O',start:0xc000,size:0x1000,type:'io'},
|
||||
{name:'ROM',start:0xd000,size:0x3000-6,type:'rom'},
|
||||
],
|
||||
},
|
||||
'apple2-e': {
|
||||
define: '__APPLE2__',
|
||||
|
@ -279,13 +213,6 @@ var PLATFORM_PARAMS = {
|
|||
data_start: 0x4e10,
|
||||
data_size: 0x1f0,
|
||||
stack_end: 0x5000,
|
||||
extra_segments:[
|
||||
{name:'BIOS',start:0x0,size:0x2000,type:'rom'},
|
||||
//{name:'Cart ROM',start:0x2000,size:0x2000,type:'rom'},
|
||||
//{name:'Magic RAM',start:0x0,size:0x4000,type:'ram'},
|
||||
{name:'Screen RAM',start:0x4000,size:0x1000,type:'ram'},
|
||||
{name:'BIOS Variables',start:0x4fce,size:0x5000-0x4fce,type:'ram'},
|
||||
],
|
||||
},
|
||||
'astrocade-arcade': {
|
||||
code_start: 0x0000,
|
||||
|
@ -306,42 +233,14 @@ var PLATFORM_PARAMS = {
|
|||
cfgfile: 'atari7800.cfg',
|
||||
libargs: ['crt0.o', 'sim6502.lib'],
|
||||
extra_link_files: ['crt0.o', 'atari7800.cfg'],
|
||||
extra_segments:[
|
||||
{name:'TIA',start:0x00,size:0x20,type:'io'},
|
||||
{name:'MARIA',start:0x20,size:0x20,type:'io'},
|
||||
{name:'RAM (6166 Block 0)',start:0x40,size:0xc0,type:'ram'},
|
||||
{name:'RAM (6166 Block 1)',start:0x140,size:0xc0,type:'ram'},
|
||||
{name:'PIA',start:0x280,size:0x18,type:'io'},
|
||||
{name:'RAM',start:0x1800,size:0x1000,type:'ram'}, // TODO: shadow ram
|
||||
{name:'Cartridge ROM',start:0x4000,size:0xc000,type:'rom'},
|
||||
],
|
||||
},
|
||||
'c64': {
|
||||
define: '__C64__',
|
||||
cfgfile: 'c64.cfg', // SYS 2058
|
||||
libargs: ['c64.lib'],
|
||||
//extra_link_files: ['c64-cart.cfg'],
|
||||
extra_segments:[
|
||||
{name:'6510 Registers',start:0x0, size:0x2,type:'io'},
|
||||
{name:'RAM', start:0x2, size:0x7ffe,type:'ram'},
|
||||
{name:'Cartridge ROM',start:0x8000,size:0x2000,type:'rom'},
|
||||
{name:'BASIC ROM', start:0xa000,size:0x2000,type:'rom'},
|
||||
{name:'RAM', start:0xc000,size:0x1000,type:'ram'},
|
||||
{name:'VIC-II I/O', start:0xd000,size:0x0400,type:'io'},
|
||||
{name:'Color RAM', start:0xd800,size:0x0400,type:'io'},
|
||||
{name:'CIA 1', start:0xdc00,size:0x0100,type:'io'},
|
||||
{name:'CIA 2', start:0xdd00,size:0x0100,type:'io'},
|
||||
{name:'KERNAL ROM', start:0xe000,size:0x2000,type:'rom'},
|
||||
],
|
||||
},
|
||||
'kim1': {
|
||||
extra_segments:[
|
||||
{name:'RAM', start:0x0000,size:0x1400,type:'ram'},
|
||||
{name:'6530', start:0x1700,size:0x0040,type:'io'},
|
||||
{name:'6530', start:0x1740,size:0x0040,type:'io'},
|
||||
{name:'RAM', start:0x1780,size:0x0080,type:'ram'},
|
||||
{name:'BIOS', start:0x1800,size:0x0800,type:'rom'},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -902,13 +801,11 @@ function assembleDASM(step:BuildStep) {
|
|||
lst.lines = [];
|
||||
}
|
||||
}
|
||||
var segments = step.params.extra_segments;
|
||||
return {
|
||||
output:aout,
|
||||
listings:listings,
|
||||
errors:errors,
|
||||
symbolmap:symbolmap,
|
||||
segments:segments
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1083,7 +980,8 @@ function linkLD65(step:BuildStep) {
|
|||
}
|
||||
// build segment map
|
||||
var seg_re = /^__(\w+)_SIZE__$/;
|
||||
var segments = [].concat(params.extra_segments||[]);
|
||||
// TODO: move to Platform class
|
||||
var segments = [];
|
||||
segments.push({name:'CPU Stack',start:0x100,size:0x100,type:'ram'});
|
||||
segments.push({name:'CPU Vectors',start:0xfffa,size:0x6,type:'rom'});
|
||||
// TODO: CHR, banks, etc
|
||||
|
@ -1402,7 +1300,7 @@ function linkSDLDZ80(step:BuildStep)
|
|||
}
|
||||
// build segment map
|
||||
var seg_re = /^s__(\w+)$/;
|
||||
var segments = [].concat(params.extra_segments||[]);
|
||||
var segments = [];
|
||||
// TODO: use stack params for stack segment
|
||||
for (let ident in symbolmap) {
|
||||
let m = seg_re.exec(ident);
|
||||
|
@ -2026,23 +1924,21 @@ function assembleXASM6809(step:BuildStep) {
|
|||
var asmlines = parseListing(alst, /^\s*([0-9]+) .+ ([0-9A-F]+)\s+\[([0-9 ]+)\]\s+([0-9A-F]+) (.*)/i, 1, 2, 4, 3);
|
||||
var listings = {};
|
||||
listings[step.prefix+'.lst'] = {lines:asmlines, text:alst};
|
||||
var segments = step.params.extra_segments;
|
||||
return {
|
||||
output:aout,
|
||||
listings:listings,
|
||||
errors:errors,
|
||||
symbolmap:symbolmap,
|
||||
segments:segments
|
||||
};
|
||||
}
|
||||
|
||||
// http://www.nespowerpak.com/nesasm/
|
||||
function assembleNESASM(step:BuildStep) {
|
||||
loadNative("nesasm");
|
||||
var re_filename = /[#](\d+)\s+(\S+)/;
|
||||
var re_filename = /\#\[(\d+)\]\s+(\S+)/;
|
||||
var re_insn = /\s+(\d+)\s+([0-9A-F]+):([0-9A-F]+)/;
|
||||
var re_error = /\s+(.+)/;
|
||||
var errors = [];
|
||||
var errors : WorkerError[] = [];
|
||||
var state = 0;
|
||||
var lineno = 0;
|
||||
var filename;
|
||||
|
@ -2063,7 +1959,7 @@ function assembleNESASM(step:BuildStep) {
|
|||
case 1:
|
||||
m = re_error.exec(s);
|
||||
if (m) {
|
||||
errors.push({line:lineno, msg:m[1]});
|
||||
errors.push({path:filename, line:lineno, msg:m[1]});
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
|
@ -2125,13 +2021,11 @@ function assembleNESASM(step:BuildStep) {
|
|||
}
|
||||
}
|
||||
}
|
||||
var segments = step.params.extra_segments;
|
||||
return {
|
||||
output:aout,
|
||||
listings:listings,
|
||||
errors:errors,
|
||||
symbolmap:symbolmap,
|
||||
segments:segments
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,69 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
|
||||
var emu = require("gen/devices.js");
|
||||
var MOS6502 = require("gen/cpu/MOS6502.js");
|
||||
var testbin = fs.readFileSync('test/cli/6502/6502_functional_test.bin', null);
|
||||
|
||||
describe('MOS6502', function() {
|
||||
it('Should pass functional tests', function() {
|
||||
assert.equal(65536, testbin.length);
|
||||
var mem = new Uint8Array(testbin);
|
||||
var bus = {
|
||||
read: (a) => { return mem[a]; },
|
||||
write: (a,v) => { mem[a] = v; }
|
||||
};
|
||||
var cpu = new MOS6502.MOS6502();
|
||||
cpu.connectMemoryBus(bus);
|
||||
cpu.reset();
|
||||
var s0 = cpu.saveState();
|
||||
s0.PC = 0x400;
|
||||
cpu.loadState(s0);
|
||||
for (var i=0; i<100000000; i++) {
|
||||
//console.log(cpu.isStable(), cpu.saveState().o);
|
||||
cpu.advanceClock();
|
||||
var pc = cpu.getPC();
|
||||
if (pc == 0x3469) break; // success!
|
||||
}
|
||||
console.log(i+' cycles, PC = $'+pc.toString(16));
|
||||
assert.equal(pc, 0x3469);
|
||||
// NMI trap
|
||||
cpu.interrupt(1);
|
||||
for (var i=0; i<20; i++) {
|
||||
cpu.advanceClock();
|
||||
var pc = cpu.getPC();
|
||||
if (pc == 0x379e) break;
|
||||
}
|
||||
assert.equal(pc, 0x379e);
|
||||
// hooks
|
||||
mem.set(testbin);
|
||||
cpu.loadState(s0);
|
||||
var pcs = [];
|
||||
var profiler = {
|
||||
logExecute: function(a) { pcs.push(a); },
|
||||
logRead: function(a) { pcs.push(a); },
|
||||
logWrite: function(a) { pcs.push(a); },
|
||||
};
|
||||
// test hooks
|
||||
var chook = new emu.CPUClockHook(cpu, profiler);
|
||||
for (var i=0; i<10000; i++) {
|
||||
cpu.advanceClock();
|
||||
}
|
||||
chook.unhook();
|
||||
for (var i=0; i<100000; i++) {
|
||||
cpu.advanceClock();
|
||||
}
|
||||
console.log(pcs.slice(pcs.length-10));
|
||||
assert.equal(10000, pcs.length);
|
||||
// bus hook
|
||||
var bhook = new emu.BusHook(bus, profiler);
|
||||
for (var i=0; i<10000; i++) {
|
||||
cpu.advanceClock();
|
||||
}
|
||||
bhook.unhook();
|
||||
console.log(pcs.slice(pcs.length-10));
|
||||
assert.equal(20000, pcs.length);
|
||||
});
|
||||
});
|
||||
|
|
@ -27,11 +27,14 @@ includeInThisContext('tss/js/tss/AudioLooper.js');
|
|||
//includeInThisContext("jsnes/dist/jsnes.min.js");
|
||||
global.jsnes = require("jsnes/dist/jsnes.min.js");
|
||||
|
||||
//var devices = require('gen/devices.js');
|
||||
var emu = require('gen/emu.js');
|
||||
var Keys = emu.Keys;
|
||||
var audio = require('gen/audio.js');
|
||||
var recorder = require('gen/recorder.js');
|
||||
//var _6502 = require('gen/cpu/MOS6502.js');
|
||||
var _apple2 = require('gen/platform/apple2.js');
|
||||
//var m_apple2 = require('gen/machine/apple2.js');
|
||||
var _vcs = require('gen/platform/vcs.js');
|
||||
var _nes = require('gen/platform/nes.js');
|
||||
var _vicdual = require('gen/platform/vicdual.js');
|
||||
|
@ -118,6 +121,11 @@ function testPlatform(platid, romname, maxframes, callback) {
|
|||
var rom = fs.readFileSync('./test/roms/' + platid + '/' + romname);
|
||||
rom = new Uint8Array(rom);
|
||||
platform.loadROM("ROM", rom);
|
||||
var state0a = platform.saveState();
|
||||
platform.reset(); // reset again
|
||||
var state0b = platform.saveState();
|
||||
//TODO: vcs fails assert.deepEqual(state0a, state0b);
|
||||
//if (platform.startProbing) platform.startProbing();
|
||||
platform.resume(); // so that recorder works
|
||||
platform.setRecorder(rec);
|
||||
for (var i=0; i<maxframes; i++) {
|
||||
|
@ -140,6 +148,11 @@ function testPlatform(platid, romname, maxframes, callback) {
|
|||
assert.equal(maxframes, rec.loadFrame(maxframes));
|
||||
var state2 = platform.saveState();
|
||||
assert.deepEqual(state1, state2);
|
||||
// test memory reads not clearing stuff
|
||||
for (var i=0; i<=0xffff; i++)
|
||||
if (platform.readAddress) platform.readAddress(i);
|
||||
var state3 = platform.saveState();
|
||||
assert.deepEqual(state2, state3);
|
||||
// test debug info
|
||||
var debugs = platform.getDebugCategories();
|
||||
for (var dcat of debugs) {
|
||||
|
@ -171,7 +184,7 @@ describe('Platform Replay', () => {
|
|||
keycallback(32, 32, 128); // space bar
|
||||
}
|
||||
});
|
||||
assert.equal(platform.saveState().kbd, 0x20); // strobe cleared
|
||||
assert.equal(platform.saveState().kbdlatch, 0x20); // strobe cleared
|
||||
});
|
||||
|
||||
it('Should run > 120 secs', () => {
|
||||
|
@ -233,7 +246,7 @@ describe('Platform Replay', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
it('Should run williams 6809', () => {
|
||||
var platform = testPlatform('williams', 'vidfill.asm.rom', 72, (platform, frameno) => {
|
||||
if (frameno == 62) {
|
||||
|
@ -241,6 +254,7 @@ describe('Platform Replay', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
it('Should run williams-z80', () => {
|
||||
var platform = testPlatform('williams-z80', 'game1.c.rom', 72, (platform, frameno) => {
|
||||
if (frameno == 62) {
|
||||
|
@ -248,7 +262,6 @@ describe('Platform Replay', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
/*
|
||||
it('Should run sound_williams', () => {
|
||||
var platform = testPlatform('sound_williams-z80', 'swave.c.rom', 72, (platform, frameno) => {
|
||||
if (frameno == 60) {
|
||||
|
@ -256,7 +269,6 @@ describe('Platform Replay', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it('Should run astrocade', () => {
|
||||
var platform = testPlatform('astrocade', 'cosmic.c.rom', 92, (platform, frameno) => {
|
||||
|
@ -285,5 +297,7 @@ describe('Platform Replay', () => {
|
|||
keycallback(Keys.VK_DOWN.c, Keys.VK_DOWN.c, 1);
|
||||
}
|
||||
});
|
||||
assert.equal(0x1800, platform.saveState().maria.dll);
|
||||
assert.equal(39, platform.readAddress(0x81)); // player y pos
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
|
||||
var emu = require("gen/devices.js");
|
||||
var ZilogZ80 = require("gen/cpu/ZilogZ80.js");
|
||||
var testbin = fs.readFileSync('test/cli/z80/zexall.bin', null);
|
||||
var runall = false;
|
||||
|
||||
describe('ZilogZ80', function() {
|
||||
it('Should pass functional tests', function() {
|
||||
assert.equal(8590, testbin.length);
|
||||
var mem = new Uint8Array(65536);
|
||||
mem.set(testbin, 0x100);
|
||||
var bus = {
|
||||
read: (a) => { return mem[a]; },
|
||||
write: (a,v) => { mem[a] = v; }
|
||||
};
|
||||
var iobus = {
|
||||
read: (a) => { return 0; },
|
||||
write: (a,v) => { }
|
||||
};
|
||||
mem[0] = 0xC3;
|
||||
mem[1] = 0x00;
|
||||
mem[2] = 0x01; // JP 0x100 CP/M TPA
|
||||
mem[5] = 0xC9; // Return from BDOS call
|
||||
var cpu = new ZilogZ80.Z80();
|
||||
cpu.connectMemoryBus(bus);
|
||||
cpu.connectIOBus(bus);
|
||||
cpu.reset();
|
||||
let cycles = 0;
|
||||
let finish = false;
|
||||
var maxcyc = runall ? 10000000000 : 10000000;
|
||||
for (var i=0; i<maxcyc; i++) {
|
||||
cycles += cpu.advanceInsn(1);
|
||||
var pc = cpu.getPC();
|
||||
if (pc == 0x5) { // BDOS call
|
||||
var regC = cpu.saveState().BC & 0xff;
|
||||
console.log(cycles, pc, regC);
|
||||
switch (regC) {
|
||||
case 0: // reset
|
||||
finish = true;
|
||||
break;
|
||||
case 9: // print
|
||||
var regDE = cpu.saveState().DE;
|
||||
var s = "";
|
||||
while (mem[regDE] != 0x24/*'$'*/) {
|
||||
s += String.fromCharCode(mem[regDE++] & 0x7f);
|
||||
}
|
||||
console.log(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("runall", runall);
|
||||
assert.equal(finish, runall);
|
||||
});
|
||||
});
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -1,26 +0,0 @@
|
|||
|
||||
function assert(b, msg) {
|
||||
if (!b) { throw new Error(msg); }
|
||||
}
|
||||
|
||||
describe('Test VCS emulator', function() {
|
||||
var platform = new VCSPlatform();
|
||||
it('Should start', function(done) {
|
||||
platform.start();
|
||||
assert(!platform.isRunning());
|
||||
// TODO: more testing
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Space Invaders emulator', function() {
|
||||
var platform = new Midway8080BWPlatform($('#emulator')[0]);
|
||||
it('Should start', function(done) {
|
||||
platform.start();
|
||||
assert(!platform.isRunning());
|
||||
platform.resume();
|
||||
assert(platform.isRunning());
|
||||
// TODO: more testing
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
|
||||
//var IDEURL = 'https://8bitworkshop.com/dev/';
|
||||
var IDEURL = 'http://localhost:8000/';
|
||||
|
||||
function testCompile(browser, platform_id, platform_name) {
|
||||
// wait for page to load
|
||||
browser
|
||||
.waitForElementVisible('body')
|
||||
.waitForElementVisible('#booksMenuButton')
|
||||
.waitForElementVisible('#preset_select')
|
||||
.assert.containsText('#platformsMenuButton', platform_name)
|
||||
.waitForElementNotVisible('#compile_spinner')
|
||||
.waitForElementNotVisible('#error_alert')
|
||||
.waitForElementNotPresent('.bootbox-alert');
|
||||
|
||||
if (platform_id == 'vcs') {
|
||||
browser.waitForElementVisible('#javatari-screen');
|
||||
} else {
|
||||
browser.waitForElementVisible('#emuscreen');
|
||||
browser.waitForElementVisible('.emuvideo');
|
||||
browser.waitForElementVisible('a[data-wndid="#memmap"]');
|
||||
}
|
||||
}
|
||||
|
||||
function testDebugging(browser) {
|
||||
// do some debugging
|
||||
browser
|
||||
.waitForElementVisible('#dbg_go.btn_active')
|
||||
.click('#dbg_step')
|
||||
.waitForElementVisible('#dbg_step.btn_stopped')
|
||||
.waitForElementVisible('#mem_info')
|
||||
.click('#dbg_reset')
|
||||
.waitForElementVisible('#dbg_reset.btn_stopped')
|
||||
.waitForElementVisible('#mem_info')
|
||||
.click('#dbg_tovsync')
|
||||
.waitForElementVisible('#dbg_tovsync.btn_stopped')
|
||||
.waitForElementVisible('#mem_info')
|
||||
.click('#dbg_go')
|
||||
.waitForElementVisible('#dbg_go.btn_active')
|
||||
.waitForElementNotVisible('#mem_info');
|
||||
/*
|
||||
.click('a[data-wndid="#disasm"]')
|
||||
.click('a[data-wndid="#memory"]')
|
||||
.click('a[data-wndid="#memmap"]')
|
||||
*/
|
||||
}
|
||||
|
||||
function testTourDialog(browser) {
|
||||
// tour dialog dismiss
|
||||
browser
|
||||
.waitForElementVisible('#step-0')
|
||||
.click('button[data-role="end"]');
|
||||
}
|
||||
|
||||
function testPlatform(exports, platform_id, platform_name, numPresets) {
|
||||
exports['load_'+platform_id] = function(browser) {
|
||||
browser.url(IDEURL + '?platform='+platform_id+"&file=NOEXIST");
|
||||
browser.waitForElementVisible('.bootbox-alert'); // test unknown file alert (TODO: check text)
|
||||
browser.waitForElementNotVisible('#error_alert'); // but it will build anyway
|
||||
testTourDialog(browser);
|
||||
}
|
||||
for (let i=1; i<=numPresets; i++) {
|
||||
exports['load_'+platform_id+'_'+i] = function(browser) {
|
||||
browser.click('#preset_select option:nth-child('+(i+1)+')');
|
||||
testCompile(browser, platform_id, platform_name);
|
||||
browser.getTitle( (title) => { console.log(title); } );
|
||||
testDebugging(browser);
|
||||
}
|
||||
}
|
||||
exports['end_'+platform_id] = function(browser) {
|
||||
browser.end();
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
testPlatform(this, 'vcs', 'Atari 2600', 35);
|
||||
testPlatform(this, 'nes', 'NES', 30);
|
||||
testPlatform(this, 'vicdual', 'VIC Dual', 7);
|
||||
testPlatform(this, 'mw8080bw', 'Midway 8080', 3);
|
||||
testPlatform(this, 'galaxian-scramble', 'Galaxian/Scramble', 3);
|
||||
testPlatform(this, 'vector-z80color', 'Atari Color Vector (Z80)', 3);
|
||||
testPlatform(this, 'williams-z80', 'Williams (Z80)', 3);
|
||||
// TODO testPlatform(this, 'sound_williams-z80', 'Williams Sound (Z80)', 1);
|
||||
testPlatform(this, 'coleco', 'ColecoVision', 12);
|
||||
//testPlatform(this, 'sms-sg1000-libcv', 'Sega SG-1000', 3);
|
||||
testPlatform(this, 'sms-sms-libcv', 'Sega Master System', 2);
|
||||
testPlatform(this, 'atari7800', 'Atari 7800', 1);
|
||||
testPlatform(this, 'astrocade', 'Bally Astrocade', 12);
|
||||
testPlatform(this, 'apple2', 'Apple ][+', 10);
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../bitmap-fonts
|
|
@ -1 +0,0 @@
|
|||
../../ibmfonts
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var fs = require('fs'),
|
||||
PNG = require('pngjs').PNG,
|
||||
RgbQuant = require('rgbquant');
|
||||
|
||||
var data = fs.readFileSync(process.argv[2]);
|
||||
var png = PNG.sync.read(data);
|
||||
q = new RgbQuant();
|
||||
q.colors = 4;
|
||||
q.sample(png.data);
|
||||
pal = q.palette(false, true);
|
||||
//console.log(q);
|
||||
|
||||
function readfonttxt(s) {
|
||||
var lines = s.split(/\r?\n/);// TODO
|
||||
}
|
||||
|
||||
function remapBits(x, arr) {
|
||||
if (!arr) return x;
|
||||
var y = 0;
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
var s = arr[i];
|
||||
if (s < 0) {
|
||||
s = -s-1;
|
||||
y ^= 1 << s;
|
||||
}
|
||||
if (x & (1 << i)) {
|
||||
y ^= 1 << s;
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
function reorder(arrin, remap) {
|
||||
var out = [];
|
||||
for (var i=0; i<arrin.length; i++) {
|
||||
var j = remapBits(i, remap);
|
||||
//console.log(i,j);
|
||||
if (j >= arrin.length) throw i+" -> "+j+" >= "+arrin.length;
|
||||
out.push(arrin[j] | 0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function packbits(arrin, bpp, brev) {
|
||||
var out = new Uint8Array((arrin.length * bpp) >> 3);
|
||||
for (var i=0; i<arrin.length; i++) {
|
||||
var j = (i * bpp) >> 3;
|
||||
var s = (i * bpp) & 7;
|
||||
if (brev) s = 8-bpp-s;
|
||||
out[j] |= arrin[i] << s;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function hex(n) {
|
||||
return (n < 16 ? "0" : "") + n.toString(16);
|
||||
}
|
||||
|
||||
function dump(arr, bpl) {
|
||||
var s = '';
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
if (i % bpl == 0) {
|
||||
s += '\n.byte ';
|
||||
} else {
|
||||
s += ',';
|
||||
}
|
||||
s += '$' + hex(arr[i]);
|
||||
}
|
||||
s += '\n';
|
||||
return s;
|
||||
}
|
||||
|
||||
idximg = q.reduce(png.data, 2);
|
||||
//idximg = idximg.slice(0, 8*16*128);
|
||||
//idximg = idximg.slice(8*16*64);
|
||||
idx2 = reorder(idximg, [0,1,2,3,4,5,6,11,12,13,-8,-9,-10,-11]); // 8x16 font
|
||||
//idx2 = reorder(idximg, [0,1,2,3,5,6,11,12,13,99,-8,-9,-10,-5]); // 8x16 CHR tileset
|
||||
idx3 = packbits(idx2, 2, true);
|
||||
//console.log(idximg.length, idx2.length, idx3.length);
|
||||
console.log(dump(idx3, 8));
|
||||
console.log(";;",idx3.length,"bytes,",pal.length/4,'colors');
|
|
@ -12,10 +12,12 @@
|
|||
"noImplicitThis": false,
|
||||
"noImplicitAny": false,
|
||||
"preserveConstEnums": true,
|
||||
"alwaysStrict": true
|
||||
"alwaysStrict": true,
|
||||
"strictNullChecks": false
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./test/**/*.ts",
|
||||
"./localForage/typings/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue