Merge branch 'newemu' for new-style emulators in src/machine

This commit is contained in:
Steven Hugg 2019-09-11 19:03:43 -05:00
commit 10f638bc4d
77 changed files with 18216 additions and 5532 deletions

1
.gitignore vendored
View File

@ -5,4 +5,5 @@ local
release
gen
test/output
tests_output/
.DS_Store

3
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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

66
lib/bootstrap-tourist.css vendored Executable file
View File

@ -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;
}

2455
lib/bootstrap-tourist.js vendored Executable file

File diff suppressed because it is too large Load Diff

23
nightwatch.json Normal file
View File

@ -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
}
}
}
}

963
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

833
presets/apple2/farmhouse.c Normal file
View File

@ -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;
}

View File

@ -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

1186
presets/apple2/yum.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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

59
presets/atari7800/dlist.c Normal file
View File

@ -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]++;
}
}

27
presets/atari7800/wsync.c Normal file
View File

@ -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++;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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));
}
}

1970
src/cpu/MOS6502.ts Normal file

File diff suppressed because it is too large Load Diff

3423
src/cpu/ZilogZ80.ts Normal file

File diff suppressed because it is too large Load Diff

396
src/devices.ts Normal file
View File

@ -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; }
}

View File

@ -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

1004
src/machine/apple2.ts Normal file

File diff suppressed because it is too large Load Diff

628
src/machine/astrocade.ts Normal file

File diff suppressed because one or more lines are too long

661
src/machine/atari7800.ts Normal file
View File

@ -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);
}

563
src/machine/c64.ts Normal file

File diff suppressed because one or more lines are too long

160
src/machine/coleco.ts Normal file
View File

@ -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=`;

148
src/machine/kim1.ts Normal file
View File

@ -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`;

389
src/machine/msx.ts Normal file
View File

@ -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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
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==
`;

151
src/machine/mw8080bw.ts Normal file
View File

@ -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;
}
}

225
src/machine/sms.ts Normal file
View File

@ -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);
}
}
}

98
src/machine/vdp_z80.ts Normal file
View File

@ -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];
}
}

175
src/machine/vicdual.ts Normal file
View File

@ -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

View File

@ -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

View File

@ -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 = `
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=`;
/// MAME support
class ColecoVisionMAMEPlatform extends BaseMAMEPlatform implements Platform {

View File

@ -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();
}

View File

@ -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'},
] } };
}
///

View File

@ -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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
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==
`;

View File

@ -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;

View File

@ -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

View File

@ -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); }
}
///

View File

@ -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;

View File

@ -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;
}
}
////////////////

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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]);
}

View File

@ -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 {

View File

@ -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

View File

@ -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.');
});
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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);
});
});

View File

@ -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
});
});

View File

@ -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);
});
});

BIN
test/cli/z80/zexall.bin Normal file

Binary file not shown.

BIN
test/cli/z80/zexdoc.bin Normal file

Binary file not shown.

View File

@ -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();
});
});

View File

@ -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);

View File

@ -1 +0,0 @@
../../bitmap-fonts

View File

@ -1 +0,0 @@
../../ibmfonts

83
tools/png2a7800.js Executable file
View File

@ -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');

View File

@ -12,10 +12,12 @@
"noImplicitThis": false,
"noImplicitAny": false,
"preserveConstEnums": true,
"alwaysStrict": true
"alwaysStrict": true,
"strictNullChecks": false
},
"include": [
"./src/**/*.ts",
"./test/**/*.ts",
"./localForage/typings/*.ts"
]
}