Compare commits
5 Commits
59b40b3ac8
...
0216e75e7f
Author | SHA1 | Date |
---|---|---|
Will Scullin | 0216e75e7f | |
Will Scullin | 88bd7836bc | |
Will Scullin | ca3f255a87 | |
Will Scullin | 3f6cfd67b7 | |
Will Scullin | f2dd9b96a3 |
|
@ -10,6 +10,10 @@ trim_trailing_whitespace = true
|
|||
[*.js]
|
||||
indent_size = 4
|
||||
|
||||
[*.ts]
|
||||
indent_size = 2
|
||||
quote_type = single
|
||||
|
||||
[*.html]
|
||||
indent_size = 2
|
||||
|
||||
|
|
211
.eslintrc.json
211
.eslintrc.json
|
@ -1,20 +1,60 @@
|
|||
{
|
||||
// Global
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"globals": {
|
||||
"tapes": "writable"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
2,
|
||||
4
|
||||
"error",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single"
|
||||
"error",
|
||||
"single",
|
||||
{ "avoidEscape": true }
|
||||
],
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"prefer-const": [
|
||||
"error"
|
||||
],
|
||||
"no-var": "error",
|
||||
"no-use-before-define": "off",
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
"allow": [
|
||||
"info",
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
],
|
||||
// Jest configuration
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
"assertFunctionNames": [
|
||||
"expect*",
|
||||
"checkImageData",
|
||||
"testCode"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
|
@ -22,16 +62,109 @@
|
|||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"tapes": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
// All overrides matching a file are applied in-order, with the last
|
||||
// taking precedence.
|
||||
//
|
||||
// TypeScript/TSX-specific configuration
|
||||
{
|
||||
"files": [ "bin/*", "babel.config.js", "webpack.config.js" ],
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint/eslint-plugin"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
// recommended is just "warn"
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
// enforce semicolons at ends of statements
|
||||
"semi": "off",
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
// enforce semicolons to separate members
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
// definitions must come before uses for variables
|
||||
"@typescript-eslint/no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false
|
||||
}
|
||||
],
|
||||
// no used variables
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
// no redeclaration of classes, members or variables
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": [
|
||||
"error"
|
||||
],
|
||||
// allow empty interface definitions and empty extends
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
// allow explicit type declaration
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
// allow some non-string types in templates
|
||||
"@typescript-eslint/restrict-template-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowNumber": true,
|
||||
"allowBoolean": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
},
|
||||
// UI elements
|
||||
{
|
||||
"files": [
|
||||
"js/ui/**.ts"
|
||||
],
|
||||
"rules": {
|
||||
// allow non-null assertions since these classes reference the DOM
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
},
|
||||
// JS Node configuration
|
||||
{
|
||||
"files": [
|
||||
"bin/*",
|
||||
"babel.config.js",
|
||||
"webpack.config.js"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
},
|
||||
|
@ -40,17 +173,49 @@
|
|||
"jquery": false,
|
||||
"browser": false
|
||||
}
|
||||
}, {
|
||||
"files": [ "test/*"],
|
||||
},
|
||||
// Test configuration
|
||||
{
|
||||
"files": [
|
||||
"test/**/*"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true
|
||||
"jest": true,
|
||||
"jasmine": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
}, {
|
||||
"files": [ "js/entry1.js"],
|
||||
},
|
||||
// Entry point configuration
|
||||
{
|
||||
"files": [
|
||||
"js/entry2.ts",
|
||||
"js/entry2e.ts",
|
||||
"jest.config.js"
|
||||
],
|
||||
"env": {
|
||||
"commonjs": true
|
||||
}
|
||||
},
|
||||
// Worker configuration
|
||||
{
|
||||
"files": [
|
||||
"workers/*"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "workers/tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"coverage/**/*"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "h",
|
||||
"version": "16"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,22 @@ on: [push]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: npm install, build, and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run build --if-present
|
||||
npm test
|
||||
env:
|
||||
CI: true
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: npm install, build, and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run build --if-present
|
||||
npm test
|
||||
env:
|
||||
CI: true
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
module.exports = {
|
||||
'moduleNameMapper': {
|
||||
'^js/(.*)': '<rootDir>/js/$1',
|
||||
'^test/(.*)': '<rootDir>/test/$1',
|
||||
'\\.css$': 'identity-obj-proxy',
|
||||
'\\.scss$': 'identity-obj-proxy',
|
||||
},
|
||||
'roots': [
|
||||
'js/',
|
||||
'test/',
|
||||
],
|
||||
'testMatch': [
|
||||
'**/?(*.)+(spec|test).+(ts|js|tsx)'
|
||||
],
|
||||
'transform': {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
'^.*\\.tsx$': 'ts-jest',
|
||||
},
|
||||
'transformIgnorePatterns': [
|
||||
'/node_modules/(?!(@testing-library/preact/dist/esm)/)',
|
||||
],
|
||||
'coveragePathIgnorePatterns': [
|
||||
'/node_modules/',
|
||||
'/js/roms/',
|
||||
'/test/',
|
||||
],
|
||||
'preset': 'ts-jest',
|
||||
};
|
342
js/apple1.ts
342
js/apple1.ts
|
@ -1,22 +1,35 @@
|
|||
import MicroModal from "micromodal";
|
||||
/* Copyright 2010-2023 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided 'as is' without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import Apple1IO from "./apple1io";
|
||||
import CPU6502 from "./cpu6502";
|
||||
import Prefs from "./prefs";
|
||||
import RAM from "./ram";
|
||||
import { TextPage } from "./canvas1";
|
||||
import { debug, hup } from "./util";
|
||||
import MicroModal from 'micromodal';
|
||||
|
||||
import Basic from "./roms/basic";
|
||||
import Bios from "./roms/bios";
|
||||
import Krusader from "./roms/krusader";
|
||||
import Apple1IO from './apple1io';
|
||||
import CPU6502 from './cpu6502';
|
||||
import Prefs from './prefs';
|
||||
import RAM from './ram';
|
||||
import { TextPage } from './canvas1';
|
||||
import { debug, hup } from './util';
|
||||
|
||||
import ACI from "./cards/aci";
|
||||
import Basic from './roms/basic';
|
||||
import Bios from './roms/bios';
|
||||
import Krusader from './roms/krusader';
|
||||
|
||||
import { mapKeyEvent, KeyBoard } from "./ui/keyboard";
|
||||
import { address, byte } from "./types";
|
||||
import ACI from './cards/aci';
|
||||
|
||||
import { mapKeyEvent, KeyBoard } from './ui/keyboard';
|
||||
import { address, byte } from './types';
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let DEBUG = false;
|
||||
// eslint-disable-next-line prefer-const
|
||||
let TRACE = true;
|
||||
const skidmarks: string[] = [];
|
||||
|
||||
|
@ -32,22 +45,22 @@ const prefs = new Prefs();
|
|||
let runTimer: ReturnType<typeof setInterval> | null = null;
|
||||
const cpu = new CPU6502();
|
||||
|
||||
var krusader = window.location.hash == "#krusader";
|
||||
const krusader = window.location.hash === '#krusader';
|
||||
|
||||
var raml, ramh, rom, aci: ACI, io: Apple1IO, text: TextPage, keyboard: KeyBoard;
|
||||
let ramh, rom;
|
||||
|
||||
// 32K base memory. Should be 0x0f for 4K, 0x1f for 8K, 0x3f for 16K
|
||||
raml = new RAM(0x00, 0x7f);
|
||||
text = new TextPage();
|
||||
const raml = new RAM(0x00, 0x7f);
|
||||
const text = new TextPage();
|
||||
text.init();
|
||||
|
||||
aci = new ACI(cpu, {
|
||||
const aci = new ACI(cpu, {
|
||||
progress: function (val) {
|
||||
document.querySelector<HTMLElement>("#tape")!.style.width =
|
||||
val * 100 + "px";
|
||||
document.querySelector<HTMLElement>('#tape')!.style.width =
|
||||
val * 100 + 'px';
|
||||
},
|
||||
});
|
||||
io = new Apple1IO(text);
|
||||
const io = new Apple1IO(text);
|
||||
|
||||
if (krusader) {
|
||||
ramh = null;
|
||||
|
@ -57,7 +70,7 @@ if (krusader) {
|
|||
ramh = new Basic();
|
||||
rom = new Bios();
|
||||
}
|
||||
keyboard = new KeyBoard("#keyboard", cpu, io, text);
|
||||
const keyboard = new KeyBoard('#keyboard', cpu, io, text);
|
||||
|
||||
cpu.addPageHandler(raml);
|
||||
if (ramh) {
|
||||
|
@ -68,7 +81,7 @@ cpu.addPageHandler(rom);
|
|||
cpu.addPageHandler(aci);
|
||||
cpu.addPageHandler(io);
|
||||
|
||||
var showFPS = false;
|
||||
let showFPS = false;
|
||||
|
||||
interface Tape {
|
||||
script: string;
|
||||
|
@ -86,7 +99,7 @@ declare global {
|
|||
//aci.setData([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef])
|
||||
|
||||
//aci.setData(tapes['BASIC']);
|
||||
aci.setData(window.tapes["Microchess"].tracks);
|
||||
aci.setData(window.tapes['Microchess'].tracks);
|
||||
|
||||
// Audio Buffer Source
|
||||
declare global {
|
||||
|
@ -99,55 +112,61 @@ const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|||
const context = new AudioContext();
|
||||
|
||||
export function doLoadLocal(files: FileList) {
|
||||
context.resume();
|
||||
files =
|
||||
files || document.querySelector<HTMLInputElement>("#local_file")!.files;
|
||||
if (files.length == 1) {
|
||||
var file = files[0];
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function (ev) {
|
||||
context.decodeAudioData(
|
||||
ev.target!.result as ArrayBuffer,
|
||||
function (buffer) {
|
||||
var buf = [];
|
||||
var data = buffer.getChannelData(0);
|
||||
var old = data[0] > 0.25;
|
||||
var last = 0;
|
||||
for (var idx = 1; idx < data.length; idx++) {
|
||||
var current = data[idx] > 0.25;
|
||||
if (current != old) {
|
||||
var delta = idx - last;
|
||||
buf.push(Math.floor((delta / buffer.sampleRate) * 1023000));
|
||||
old = current;
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
aci.buffer = buf;
|
||||
MicroModal.close("local-modal");
|
||||
},
|
||||
function () {
|
||||
window.alert("Unable to read tape file: " + file.name);
|
||||
}
|
||||
);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
context
|
||||
.resume()
|
||||
.then(() => {
|
||||
files =
|
||||
files || document.querySelector<HTMLInputElement>('#local_file')!.files;
|
||||
if (files.length === 1) {
|
||||
const file = files[0];
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function (ev) {
|
||||
context
|
||||
.decodeAudioData(
|
||||
ev.target!.result as ArrayBuffer,
|
||||
function (buffer) {
|
||||
const buf = [];
|
||||
const data = buffer.getChannelData(0);
|
||||
let old = data[0] > 0.25;
|
||||
let last = 0;
|
||||
for (let idx = 1; idx < data.length; idx++) {
|
||||
const current = data[idx] > 0.25;
|
||||
if (current !== old) {
|
||||
const delta = idx - last;
|
||||
buf.push(Math.floor((delta / buffer.sampleRate) * 1023000));
|
||||
old = current;
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
aci.buffer = buf;
|
||||
MicroModal.close('local-modal');
|
||||
},
|
||||
function () {
|
||||
window.alert('Unable to read tape file: ' + file.name);
|
||||
}
|
||||
)
|
||||
.catch(console.error);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function updateKHz() {
|
||||
let now = Date.now();
|
||||
let ms = now - startTime;
|
||||
let cycles = cpu.getCycles();
|
||||
const now = Date.now();
|
||||
const ms = now - startTime;
|
||||
const cycles = cpu.getCycles();
|
||||
let delta: number;
|
||||
|
||||
if (showFPS) {
|
||||
delta = renderedFrames - lastFrames;
|
||||
var fps = Math.floor(delta / (ms / 1000));
|
||||
document.querySelector("#khz")!.innerHTML = fps + "fps";
|
||||
const fps = Math.floor(delta / (ms / 1000));
|
||||
document.querySelector('#khz')!.innerHTML = fps + 'fps';
|
||||
} else {
|
||||
delta = cycles - lastCycles;
|
||||
var khz = Math.floor(delta / ms);
|
||||
document.querySelector("#khz")!.innerHTML = khz + "KHz";
|
||||
const khz = Math.floor(delta / ms);
|
||||
document.querySelector('#khz')!.innerHTML = khz + 'KHz';
|
||||
}
|
||||
|
||||
startTime = now;
|
||||
|
@ -155,9 +174,8 @@ function updateKHz() {
|
|||
lastFrames = renderedFrames;
|
||||
}
|
||||
|
||||
var loading = false;
|
||||
var throttling = true;
|
||||
var turbotape = false;
|
||||
let throttling = true;
|
||||
let turbotape = false;
|
||||
|
||||
export function toggleFPS() {
|
||||
showFPS = !showFPS;
|
||||
|
@ -165,7 +183,7 @@ export function toggleFPS() {
|
|||
|
||||
export function toggleSpeed() {
|
||||
throttling =
|
||||
document.querySelector<HTMLInputElement>("#speed_toggle")!.checked;
|
||||
document.querySelector<HTMLInputElement>('#speed_toggle')!.checked;
|
||||
if (runTimer) {
|
||||
run();
|
||||
}
|
||||
|
@ -188,17 +206,17 @@ function run(pc?: address) {
|
|||
cpu.setPC(pc);
|
||||
}
|
||||
|
||||
var ival = 30,
|
||||
step = 1023 * ival,
|
||||
stepMax = step;
|
||||
let ival = 30;
|
||||
let step = 1023 * ival;
|
||||
const stepMax = step;
|
||||
|
||||
if (!throttling) {
|
||||
ival = 1;
|
||||
}
|
||||
|
||||
var now,
|
||||
last = Date.now();
|
||||
var runFn = function () {
|
||||
let now;
|
||||
let last = Date.now();
|
||||
const runFn = function () {
|
||||
now = Date.now();
|
||||
renderedFrames++;
|
||||
step = (now - last) * 1023;
|
||||
|
@ -206,27 +224,25 @@ function run(pc?: address) {
|
|||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
if (document.location.hash != hashtag) {
|
||||
if (document.location.hash !== hashtag) {
|
||||
hashtag = document.location.hash;
|
||||
}
|
||||
if (!loading) {
|
||||
if (DEBUG) {
|
||||
cpu.stepCyclesDebug(TRACE ? 1 : step, function () {
|
||||
var line = JSON.stringify(cpu.getState());
|
||||
if (TRACE) {
|
||||
debug(line);
|
||||
} else {
|
||||
skidmarks.push(line);
|
||||
if (skidmarks.length > 256) {
|
||||
skidmarks.shift();
|
||||
}
|
||||
if (DEBUG) {
|
||||
cpu.stepCyclesDebug(TRACE ? 1 : step, function () {
|
||||
const line = JSON.stringify(cpu.getState());
|
||||
if (TRACE) {
|
||||
debug(line);
|
||||
} else {
|
||||
skidmarks.push(line);
|
||||
if (skidmarks.length > 256) {
|
||||
skidmarks.shift();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cpu.stepCycles(step);
|
||||
}
|
||||
text.blit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cpu.stepCycles(step);
|
||||
}
|
||||
text.blit();
|
||||
if (!paused) {
|
||||
requestAnimationFrame(runFn);
|
||||
}
|
||||
|
@ -255,7 +271,7 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
var _key: byte;
|
||||
let _key: byte;
|
||||
function _keydown(evt: KeyboardEvent) {
|
||||
if (evt.keyCode === 112) {
|
||||
cpu.reset();
|
||||
|
@ -263,19 +279,19 @@ function _keydown(evt: KeyboardEvent) {
|
|||
if (document.webkitIsFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else {
|
||||
var elem = document.getElementById("display");
|
||||
const elem = document.getElementById('display');
|
||||
elem!.webkitRequestFullScreen();
|
||||
}
|
||||
} else if (evt.key === "Shift") {
|
||||
} else if (evt.key === 'Shift') {
|
||||
keyboard.shiftKey(true);
|
||||
} else if (evt.key == "Control") {
|
||||
} else if (evt.key === 'Control') {
|
||||
keyboard.controlKey(true);
|
||||
} else if (!focused && (!evt.metaKey || evt.ctrlKey)) {
|
||||
evt.preventDefault();
|
||||
|
||||
var key = mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
if (_key != 0xff) io.keyUp();
|
||||
const key = mapKeyEvent(evt);
|
||||
if (key !== 0xff) {
|
||||
if (_key !== 0xff) io.keyUp();
|
||||
io.keyDown(key);
|
||||
_key = key;
|
||||
}
|
||||
|
@ -285,9 +301,9 @@ function _keydown(evt: KeyboardEvent) {
|
|||
function _keyup(evt: KeyboardEvent) {
|
||||
_key = 0xff;
|
||||
|
||||
if (evt.key === "Shift") {
|
||||
if (evt.key === 'Shift') {
|
||||
keyboard.shiftKey(false);
|
||||
} else if (evt.key === "Control") {
|
||||
} else if (evt.key === 'Control') {
|
||||
keyboard.controlKey(false);
|
||||
} else {
|
||||
if (!focused) {
|
||||
|
@ -300,9 +316,9 @@ let _updateScreenTimer: ReturnType<typeof setInterval> | null = null;
|
|||
|
||||
export function updateScreen() {
|
||||
const green =
|
||||
document.querySelector<HTMLInputElement>("#green_screen")!.checked;
|
||||
document.querySelector<HTMLInputElement>('#green_screen')!.checked;
|
||||
const scanlines =
|
||||
document.querySelector<HTMLInputElement>("#show_scanlines")!.checked;
|
||||
document.querySelector<HTMLInputElement>('#show_scanlines')!.checked;
|
||||
|
||||
text.green(green);
|
||||
text.scanlines(scanlines);
|
||||
|
@ -321,39 +337,39 @@ paused = false;
|
|||
export function pauseRun(b: HTMLButtonElement) {
|
||||
if (paused) {
|
||||
run();
|
||||
b.value = "Pause";
|
||||
b.value = 'Pause';
|
||||
} else {
|
||||
stop();
|
||||
b.value = "Run";
|
||||
b.value = 'Run';
|
||||
}
|
||||
paused = !paused;
|
||||
}
|
||||
|
||||
export function openOptions() {
|
||||
MicroModal.show("options-modal");
|
||||
MicroModal.show('options-modal');
|
||||
}
|
||||
|
||||
export function openLoadText(event?: MouseEvent) {
|
||||
if (event && event.altKey) {
|
||||
MicroModal.show("local-modal");
|
||||
MicroModal.show('local-modal');
|
||||
} else {
|
||||
MicroModal.show("input-modal");
|
||||
MicroModal.show('input-modal');
|
||||
}
|
||||
}
|
||||
|
||||
export function doLoadText() {
|
||||
var text = document.querySelector<HTMLInputElement>("#text_input")!.value;
|
||||
if (!text.indexOf("//Binary")) {
|
||||
var lines = text.split("\n");
|
||||
const text = document.querySelector<HTMLInputElement>('#text_input')!.value;
|
||||
if (!text.indexOf('//Binary')) {
|
||||
const lines = text.split('\n');
|
||||
lines.forEach(function (line) {
|
||||
var parts = line.split(": ");
|
||||
if (parts.length == 2) {
|
||||
const parts = line.split(': ');
|
||||
if (parts.length === 2) {
|
||||
let addr: address = 0;
|
||||
if (parts[0].length > 0) {
|
||||
addr = parseInt(parts[0], 16);
|
||||
}
|
||||
var data = parts[1].split(" ");
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
const data = parts[1].split(' ');
|
||||
for (let idx = 0; idx < data.length; idx++) {
|
||||
cpu.write(addr >> 8, addr & 0xff, parseInt(data[idx], 16));
|
||||
addr++;
|
||||
}
|
||||
|
@ -362,28 +378,28 @@ export function doLoadText() {
|
|||
} else {
|
||||
io.paste(text);
|
||||
}
|
||||
MicroModal.close("input-modal");
|
||||
MicroModal.close('input-modal');
|
||||
}
|
||||
|
||||
export function handleDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer!.dropEffect = "copy";
|
||||
event.dataTransfer!.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
export function handleDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var dt = event.dataTransfer;
|
||||
const dt = event.dataTransfer;
|
||||
if (dt?.files && dt.files.length > 0) {
|
||||
doLoadLocal(dt.files);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDragEnd(event: DragEvent) {
|
||||
var dt = event.dataTransfer;
|
||||
const dt = event.dataTransfer;
|
||||
if (dt?.items) {
|
||||
for (var i = 0; i < dt.items.length; i++) {
|
||||
for (let i = 0; i < dt.items.length; i++) {
|
||||
dt.items.remove(i);
|
||||
}
|
||||
} else {
|
||||
|
@ -393,39 +409,39 @@ export function handleDragEnd(event: DragEvent) {
|
|||
|
||||
MicroModal.init();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
hashtag = document.location.hash;
|
||||
|
||||
/*
|
||||
* Input Handling
|
||||
*/
|
||||
|
||||
const canvas = document.querySelector<HTMLCanvasElement>("#text")!;
|
||||
const context = canvas.getContext("2d")!;
|
||||
const canvas = document.querySelector<HTMLCanvasElement>('#text')!;
|
||||
const context = canvas.getContext('2d')!;
|
||||
|
||||
text.setContext(context);
|
||||
|
||||
window.addEventListener("keydown", _keydown);
|
||||
window.addEventListener("keyup", _keyup);
|
||||
window.addEventListener('keydown', _keydown);
|
||||
window.addEventListener('keyup', _keyup);
|
||||
|
||||
window.addEventListener("paste", (event) => {
|
||||
var paste = event.clipboardData!.getData("text/plain");
|
||||
window.addEventListener('paste', (event) => {
|
||||
const paste = event.clipboardData!.getData('text/plain');
|
||||
setKeyBuffer(paste);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener("copy", (event) => {
|
||||
event.clipboardData?.setData("text/plain", text.getText());
|
||||
window.addEventListener('copy', (event) => {
|
||||
event.clipboardData?.setData('text/plain', text.getText());
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
document.querySelectorAll("input,textarea").forEach(function (el) {
|
||||
el.addEventListener("focus", function () {
|
||||
document.querySelectorAll('input,textarea').forEach(function (el) {
|
||||
el.addEventListener('focus', function () {
|
||||
focused = true;
|
||||
});
|
||||
});
|
||||
document.querySelectorAll("input,textarea").forEach(function (el) {
|
||||
el.addEventListener("blur", function () {
|
||||
document.querySelectorAll('input,textarea').forEach(function (el) {
|
||||
el.addEventListener('blur', function () {
|
||||
focused = false;
|
||||
});
|
||||
});
|
||||
|
@ -433,58 +449,58 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
|
||||
if (prefs.havePrefs()) {
|
||||
document
|
||||
.querySelectorAll<HTMLInputElement>("input[type=checkbox]")
|
||||
.querySelectorAll<HTMLInputElement>('input[type=checkbox]')
|
||||
.forEach(function (el) {
|
||||
var val = prefs.readPref(el.id);
|
||||
if (val != null) el.checked = JSON.parse(val);
|
||||
const val = prefs.readPref(el.id);
|
||||
if (val != null) el.checked = !!JSON.parse(val);
|
||||
});
|
||||
document
|
||||
.querySelectorAll<HTMLInputElement>("input[type=checkbox]")
|
||||
.querySelectorAll<HTMLInputElement>('input[type=checkbox]')
|
||||
.forEach(function (el) {
|
||||
el.addEventListener("change", function () {
|
||||
el.addEventListener('change', function () {
|
||||
prefs.writePref(el.id, JSON.stringify(el.checked));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
turbotape = document.querySelector<HTMLInputElement>("#turbo_tape")!.checked;
|
||||
turbotape = document.querySelector<HTMLInputElement>('#turbo_tape')!.checked;
|
||||
|
||||
Object.keys(window.tapes)
|
||||
.sort()
|
||||
.forEach(function (key) {
|
||||
var option = document.createElement("option");
|
||||
const option = document.createElement('option');
|
||||
option.value = key;
|
||||
option.text = key;
|
||||
document.querySelector("#tape_select")!.append(option);
|
||||
document.querySelector('#tape_select')!.append(option);
|
||||
});
|
||||
|
||||
function doTapeSelect() {
|
||||
var tapeId =
|
||||
document.querySelector<HTMLInputElement>("#tape_select")!.value;
|
||||
var tape = window.tapes[tapeId];
|
||||
const tapeId =
|
||||
document.querySelector<HTMLInputElement>('#tape_select')!.value;
|
||||
const tape = window.tapes[tapeId];
|
||||
if (!tape) {
|
||||
document.querySelector<HTMLInputElement>("#text_input")!.value = "";
|
||||
document.querySelector<HTMLInputElement>('#text_input')!.value = '';
|
||||
return;
|
||||
}
|
||||
debug("Loading", tapeId);
|
||||
debug('Loading', tapeId);
|
||||
|
||||
window.location.hash = tapeId;
|
||||
reset();
|
||||
if (turbotape) {
|
||||
var trackIdx = 0,
|
||||
script = "";
|
||||
var parts = tape.script.split("\n");
|
||||
let trackIdx = 0,
|
||||
script = '';
|
||||
const parts = tape.script.split('\n');
|
||||
// Ignore part 0 (C100R)
|
||||
// Split part 1 into ranges
|
||||
var ranges = parts[1].split(" ");
|
||||
var idx;
|
||||
const ranges = parts[1].split(' ');
|
||||
let idx;
|
||||
for (idx = 0; idx < ranges.length; idx++) {
|
||||
var range = ranges[idx].split(".");
|
||||
var start = parseInt(range[0], 16);
|
||||
var end = parseInt(range[1], 16);
|
||||
var track = tape.tracks[trackIdx];
|
||||
var kdx = 0;
|
||||
for (var jdx = start; jdx <= end; jdx++) {
|
||||
const range = ranges[idx].split('.');
|
||||
const start = parseInt(range[0], 16);
|
||||
const end = parseInt(range[1], 16);
|
||||
const track = tape.tracks[trackIdx];
|
||||
let kdx = 0;
|
||||
for (let jdx = start; jdx <= end; jdx++) {
|
||||
cpu.write(jdx >> 8, jdx & 0xff, track[kdx++]);
|
||||
}
|
||||
trackIdx++;
|
||||
|
@ -492,29 +508,29 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
// Execute parts 2-n
|
||||
for (idx = 2; idx < parts.length; idx++) {
|
||||
if (parts[idx]) {
|
||||
script += parts[idx] + "\n";
|
||||
script += parts[idx] + '\n';
|
||||
}
|
||||
}
|
||||
document.querySelector<HTMLInputElement>("#text_input")!.value = script;
|
||||
document.querySelector<HTMLInputElement>('#text_input')!.value = script;
|
||||
} else {
|
||||
aci.setData(tape.tracks);
|
||||
document.querySelector<HTMLInputElement>("#text_input")!.value =
|
||||
document.querySelector<HTMLInputElement>('#text_input')!.value =
|
||||
tape.script;
|
||||
}
|
||||
doLoadText();
|
||||
}
|
||||
document
|
||||
.querySelector("#tape_select")!
|
||||
.addEventListener("change", doTapeSelect);
|
||||
.querySelector('#tape_select')!
|
||||
.addEventListener('change', doTapeSelect);
|
||||
|
||||
run();
|
||||
setInterval(updateKHz, 1000);
|
||||
updateScreen();
|
||||
|
||||
var tape = hup();
|
||||
const tape = hup();
|
||||
if (tape) {
|
||||
openLoadText();
|
||||
document.querySelector<HTMLInputElement>("#tape_select")!.value = tape;
|
||||
document.querySelector<HTMLInputElement>('#tape_select')!.value = tape;
|
||||
doTapeSelect();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { TextPage } from "./canvas1";
|
||||
import type { byte } from "./types";
|
||||
import { TextPage } from './canvas1';
|
||||
import type { byte } from './types';
|
||||
|
||||
const LOC = {
|
||||
KBD: 0x10,
|
||||
|
@ -36,20 +36,22 @@ export default class Apple1IO {
|
|||
return 0xd0;
|
||||
}
|
||||
read(_page: byte, off: byte): byte {
|
||||
var result = 0;
|
||||
let result = 0;
|
||||
off &= 0x13;
|
||||
switch (off) {
|
||||
case LOC.KBD:
|
||||
// Keyboard
|
||||
const key = this._buffer.shift();
|
||||
if (key != null) {
|
||||
result = key.toUpperCase().charCodeAt(0) & 0x7f;
|
||||
this._keyReady = this._buffer.length > 0;
|
||||
} else {
|
||||
result = this._key;
|
||||
this._keyReady = false;
|
||||
{
|
||||
// Keyboard
|
||||
const key = this._buffer.shift();
|
||||
if (key != null) {
|
||||
result = key.toUpperCase().charCodeAt(0) & 0x7f;
|
||||
this._keyReady = this._buffer.length > 0;
|
||||
} else {
|
||||
result = this._key;
|
||||
this._keyReady = false;
|
||||
}
|
||||
result |= 0x80;
|
||||
}
|
||||
result |= 0x80;
|
||||
break;
|
||||
case LOC.KBDRDY:
|
||||
result = this._keyReady ? 0x80 : 0x00;
|
||||
|
@ -96,9 +98,9 @@ export default class Apple1IO {
|
|||
this._keyReady = true;
|
||||
}
|
||||
paste(buffer: string) {
|
||||
buffer = buffer.replace(/\/\/.*\n/g, "");
|
||||
buffer = buffer.replace(/\n/g, "\r");
|
||||
this._buffer = buffer.split("");
|
||||
buffer = buffer.replace(/\/\/.*\n/g, '');
|
||||
buffer = buffer.replace(/\n/g, '\r');
|
||||
this._buffer = buffer.split('');
|
||||
this._keyReady = true;
|
||||
}
|
||||
}
|
||||
|
|
16
js/base64.ts
16
js/base64.ts
|
@ -1,6 +1,6 @@
|
|||
import { memory } from "./types";
|
||||
import { memory } from './types';
|
||||
|
||||
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
/** Encode an array of bytes in base64. */
|
||||
export function base64_encode(data: null | undefined): undefined;
|
||||
|
@ -37,7 +37,7 @@ export function base64_encode(
|
|||
bits,
|
||||
i = 0,
|
||||
ac = 0,
|
||||
enc = "";
|
||||
enc = '';
|
||||
const tmp_arr = [];
|
||||
|
||||
if (!data) {
|
||||
|
@ -62,14 +62,14 @@ export function base64_encode(
|
|||
B64.charAt(h1) + B64.charAt(h2) + B64.charAt(h3) + B64.charAt(h4);
|
||||
} while (i < data.length);
|
||||
|
||||
enc = tmp_arr.join("");
|
||||
enc = tmp_arr.join('');
|
||||
|
||||
switch (data.length % 3) {
|
||||
case 1:
|
||||
enc = enc.slice(0, -2) + "==";
|
||||
enc = enc.slice(0, -2) + '==';
|
||||
break;
|
||||
case 2:
|
||||
enc = enc.slice(0, -1) + "=";
|
||||
enc = enc.slice(0, -1) + '=';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -147,11 +147,11 @@ export function base64_decode(
|
|||
return new Uint8Array(tmp_arr);
|
||||
}
|
||||
|
||||
const DATA_URL_PREFIX = "data:application/octet-stream;base64,";
|
||||
const DATA_URL_PREFIX = 'data:application/octet-stream;base64,';
|
||||
|
||||
export function base64_json_parse(json: string): unknown {
|
||||
const reviver = (_key: string, value: unknown) => {
|
||||
if (typeof value === "string" && value.startsWith(DATA_URL_PREFIX)) {
|
||||
if (typeof value === 'string' && value.startsWith(DATA_URL_PREFIX)) {
|
||||
return base64_decode(value.slice(DATA_URL_PREFIX.length));
|
||||
}
|
||||
return value;
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { charset } from "./roms/apple1char";
|
||||
import type { byte } from "./types";
|
||||
import { charset } from './roms/apple1char';
|
||||
import type { byte } from './types';
|
||||
|
||||
/*
|
||||
0: A9 9 AA 20 EF FF E8 8A 4C 2 0
|
||||
|
@ -22,8 +22,8 @@ import type { byte } from "./types";
|
|||
*/
|
||||
|
||||
export class TextPage {
|
||||
_page: ImageData;
|
||||
_context: CanvasRenderingContext2D;
|
||||
_page: ImageData | undefined;
|
||||
_context: CanvasRenderingContext2D | undefined;
|
||||
|
||||
_buffer: byte[][] = [];
|
||||
_greenMode = false;
|
||||
|
@ -40,9 +40,9 @@ export class TextPage {
|
|||
|
||||
constructor() {
|
||||
this._buffer = [];
|
||||
for (var row = 0; row < 24; row++) {
|
||||
for (let row = 0; row < 24; row++) {
|
||||
this._buffer[row] = [];
|
||||
for (var col = 0; col < 40; col++) {
|
||||
for (let col = 0; col < 40; col++) {
|
||||
this._buffer[row][col] = col % 2 ? 0x00 : 0xff;
|
||||
}
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ export class TextPage {
|
|||
}
|
||||
|
||||
write(val: byte): void {
|
||||
var col;
|
||||
let col;
|
||||
val &= 0x7f;
|
||||
|
||||
if (this.transcript) {
|
||||
if (val == 0xd) {
|
||||
this.transcript += "\n";
|
||||
if (val === 0xd) {
|
||||
this.transcript += '\n';
|
||||
} else if (val >= 0x20) {
|
||||
if (val >= 0x60) {
|
||||
val &= 0x5f;
|
||||
|
@ -72,7 +72,7 @@ export class TextPage {
|
|||
}
|
||||
}
|
||||
|
||||
if (val == 0x0d) {
|
||||
if (val === 0x0d) {
|
||||
for (col = this._col; col < 40; col++) {
|
||||
this._buffer[this._row][col] = 0x20;
|
||||
}
|
||||
|
@ -98,16 +98,17 @@ export class TextPage {
|
|||
this.refresh();
|
||||
}
|
||||
writeAt(row: byte, col: byte, val: byte): void {
|
||||
if (!this._page) {
|
||||
return;
|
||||
}
|
||||
this._buffer[row][col] = val;
|
||||
var data = this._page.data,
|
||||
fore,
|
||||
back,
|
||||
color;
|
||||
var off = (col * 14 + row * 560 * 8 * 2) * 4;
|
||||
const data = this._page.data;
|
||||
let color;
|
||||
let off = (col * 14 + row * 560 * 8 * 2) * 4;
|
||||
|
||||
fore = this._greenMode ? this._green : this._white;
|
||||
back = this._black;
|
||||
var char = 0;
|
||||
let fore = this._greenMode ? this._green : this._white;
|
||||
const back = this._black;
|
||||
let char = 0;
|
||||
|
||||
if (!val) {
|
||||
if (this._blinking) {
|
||||
|
@ -118,12 +119,12 @@ export class TextPage {
|
|||
char |= val & 0x40 ? 0 : 0x20;
|
||||
}
|
||||
|
||||
for (var jdx = 0; jdx < 8; jdx++) {
|
||||
var b = charset[char * 8 + jdx];
|
||||
for (var idx = 0; idx < 7; idx += 1) {
|
||||
for (let jdx = 0; jdx < 8; jdx++) {
|
||||
let b = charset[char * 8 + jdx];
|
||||
for (let idx = 0; idx < 7; idx += 1) {
|
||||
b <<= 1;
|
||||
color = b & 0x80 ? fore : back;
|
||||
var c0 = color[0],
|
||||
const c0 = color[0],
|
||||
c1 = color[1],
|
||||
c2 = color[2];
|
||||
data[off + 0] = data[off + 4] = c0;
|
||||
|
@ -144,9 +145,9 @@ export class TextPage {
|
|||
}
|
||||
}
|
||||
_blink() {
|
||||
for (var row = 0; row < 24; row++) {
|
||||
for (var col = 0; col < 40; col++) {
|
||||
var val = this._buffer[row][col];
|
||||
for (let row = 0; row < 24; row++) {
|
||||
for (let col = 0; col < 40; col++) {
|
||||
const val = this._buffer[row][col];
|
||||
if (!val) {
|
||||
this.writeAt(row, col, val);
|
||||
}
|
||||
|
@ -155,8 +156,8 @@ export class TextPage {
|
|||
this._dirty = true;
|
||||
}
|
||||
refresh() {
|
||||
for (var row = 0; row < 24; row++) {
|
||||
for (var col = 0; col < 40; col++) {
|
||||
for (let row = 0; row < 24; row++) {
|
||||
for (let col = 0; col < 40; col++) {
|
||||
this.writeAt(row, col, this._buffer[row][col]);
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +172,9 @@ export class TextPage {
|
|||
this.refresh();
|
||||
}
|
||||
blit() {
|
||||
if (!this._page || !this._context) {
|
||||
return;
|
||||
}
|
||||
if (this._dirty) {
|
||||
this._context.putImageData(
|
||||
this._page,
|
||||
|
@ -186,7 +190,7 @@ export class TextPage {
|
|||
setContext(context: CanvasRenderingContext2D) {
|
||||
this._context = context;
|
||||
this._page = context.createImageData(560, 384);
|
||||
for (var idx = 0; idx < 560 * 384 * 4; idx++) {
|
||||
for (let idx = 0; idx < 560 * 384 * 4; idx++) {
|
||||
this._page.data[idx] = 0xff;
|
||||
}
|
||||
}
|
||||
|
@ -202,24 +206,24 @@ export class TextPage {
|
|||
return charCode;
|
||||
}
|
||||
|
||||
var buffer = "",
|
||||
let buffer = '',
|
||||
line,
|
||||
charCode;
|
||||
var row, col;
|
||||
let row, col;
|
||||
for (row = 0; row < 24; row++) {
|
||||
line = "";
|
||||
line = '';
|
||||
for (col = 0; col < 40; col++) {
|
||||
charCode = mapCharCode(this._buffer[row][col]);
|
||||
line += String.fromCharCode(charCode);
|
||||
}
|
||||
line = line.trimRight();
|
||||
buffer += line + "\n";
|
||||
buffer += line + '\n';
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
clear() {
|
||||
for (var row = 0; row < 24; row++) {
|
||||
for (var col = 0; col < 40; col++) {
|
||||
for (let row = 0; row < 24; row++) {
|
||||
for (let col = 0; col < 40; col++) {
|
||||
this._buffer[row][col] = 0x20;
|
||||
}
|
||||
}
|
||||
|
@ -228,5 +232,5 @@ export class TextPage {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
transcript = "";
|
||||
transcript = '';
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import CPU6502 from "../cpu6502";
|
||||
import { debug } from "../util";
|
||||
import { byte } from "../types";
|
||||
import CPU6502 from '../cpu6502';
|
||||
import { debug } from '../util';
|
||||
import { byte } from '../types';
|
||||
|
||||
const rom = [
|
||||
0xa9, 0xaa, 0x20, 0xef, 0xff, 0xa9, 0x8d, 0x20, 0xef, 0xff, 0xa0, 0xff, 0xc8,
|
||||
|
@ -50,19 +50,18 @@ export default class ACI {
|
|||
return 0xc1;
|
||||
}
|
||||
read(page: byte, off: byte) {
|
||||
var now = this.cpu.getCycles();
|
||||
var result = rom[off];
|
||||
if (page == 0xc0) {
|
||||
const now = this.cpu.getCycles();
|
||||
let result = rom[off];
|
||||
if (page === 0xc0) {
|
||||
if (this._recording) {
|
||||
var delta = now - this._last;
|
||||
const delta = now - this._last;
|
||||
this.buffer.push(delta);
|
||||
this._last = now;
|
||||
} else {
|
||||
var progress;
|
||||
if (this._readOffset < this.buffer.length) {
|
||||
if (now > this._next) {
|
||||
if (this._readOffset % 1000 == 0) {
|
||||
debug("Read " + this._readOffset / 1000);
|
||||
if (this._readOffset % 1000 === 0) {
|
||||
debug('Read ' + this._readOffset / 1000);
|
||||
}
|
||||
this._flip = !this._flip;
|
||||
this._next = now + this.buffer[this._readOffset++];
|
||||
|
@ -70,9 +69,9 @@ export default class ACI {
|
|||
}
|
||||
result = this._flip ? rom[off | 0x01] : rom[off & 0xfe];
|
||||
|
||||
progress =
|
||||
const progress =
|
||||
Math.round((this._readOffset / this.buffer.length) * 100) / 100;
|
||||
if (this._progress != progress) {
|
||||
if (this._progress !== progress) {
|
||||
this._progress = progress;
|
||||
this.cb.progress(this._progress);
|
||||
}
|
||||
|
@ -83,14 +82,14 @@ export default class ACI {
|
|||
case 0x00:
|
||||
this._recording = false;
|
||||
this._beKind = true;
|
||||
debug("Entering ACI CLI");
|
||||
debug('Entering ACI CLI');
|
||||
break;
|
||||
case 0x63:
|
||||
if (this._recording) {
|
||||
this.buffer.push(5000000);
|
||||
this._recording = false;
|
||||
}
|
||||
debug("Exiting ACI CLI");
|
||||
debug('Exiting ACI CLI');
|
||||
break;
|
||||
case 0x70: // WRITE
|
||||
this._recording = true;
|
||||
|
@ -98,7 +97,7 @@ export default class ACI {
|
|||
this._beKind = false;
|
||||
this.buffer = [];
|
||||
}
|
||||
debug("Start write");
|
||||
debug('Start write');
|
||||
this._last = now;
|
||||
break;
|
||||
//case 0x7c: // WBITLOOP:
|
||||
|
@ -107,7 +106,7 @@ export default class ACI {
|
|||
// break;
|
||||
case 0x8d: // READ
|
||||
this._recording = false;
|
||||
debug("Start read");
|
||||
debug('Start read');
|
||||
if (this._beKind) {
|
||||
this._readOffset = 0;
|
||||
this._next = now + 5000000;
|
||||
|
@ -131,7 +130,7 @@ export default class ACI {
|
|||
setState() {}
|
||||
|
||||
setData(data: number[][]) {
|
||||
var seg, idx, jdx, d, b;
|
||||
let seg, idx, jdx, d, b;
|
||||
this.buffer = [];
|
||||
for (seg = 0; seg < data.length; seg++) {
|
||||
for (idx = 0; idx < 16384; idx++) {
|
||||
|
|
2114
js/cpu6502.ts
2114
js/cpu6502.ts
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,3 @@
|
|||
var Apple1 = require('./apple1');
|
||||
import * as Apple1 from './apple1';
|
||||
|
||||
module.exports = { Apple1: Apple1 };
|
||||
window.Apple1 = Apple1;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
export default class Prefs {
|
||||
havePrefs() {
|
||||
return typeof localStorage !== "undefined";
|
||||
return typeof localStorage !== 'undefined';
|
||||
}
|
||||
readPref(name: string) {
|
||||
if (localStorage) return localStorage.getItem(name);
|
||||
|
|
10
js/ram.ts
10
js/ram.ts
|
@ -9,9 +9,9 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { base64_decode, base64_encode } from "./base64";
|
||||
import { allocMemPages } from "./util";
|
||||
import type { byte } from "./types";
|
||||
import { base64_decode, base64_encode } from './base64';
|
||||
import { allocMemPages } from './util';
|
||||
import type { byte } from './types';
|
||||
|
||||
export interface RAMState {
|
||||
start: byte;
|
||||
|
@ -25,8 +25,8 @@ export default class RAM {
|
|||
constructor(private start_page: byte, private end_page: byte) {
|
||||
this.mem = allocMemPages(end_page - start_page + 1);
|
||||
|
||||
for (var page = 0; page <= end_page; page++) {
|
||||
for (var off = 0; off < 0x100; off++) {
|
||||
for (let page = 0; page <= end_page; page++) {
|
||||
for (let off = 0; off < 0x100; off++) {
|
||||
this.mem[page * 0x100 + off] = 0; // Math.floor(Math.random()*256);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { byte } from "js/types";
|
||||
import { byte } from 'js/types';
|
||||
|
||||
export default class Basic {
|
||||
ram = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { byte } from "js/types";
|
||||
import { byte } from 'js/types';
|
||||
|
||||
export default class Bios {
|
||||
rom = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { byte } from "js/types";
|
||||
import { byte } from 'js/types';
|
||||
|
||||
export default class EnhancedBasic {
|
||||
rom = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { byte } from "js/types";
|
||||
import { byte } from 'js/types';
|
||||
|
||||
export default class Krusader {
|
||||
rom = [
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import type { address } from "./types";
|
||||
import type { address } from './types';
|
||||
|
||||
export const SYMBOLS: Record<address, string> = {
|
||||
0xd010: "KBD",
|
||||
0xd011: "KBDCR",
|
||||
0xd012: "DSP",
|
||||
0xd013: "DSPCR",
|
||||
0xd010: 'KBD',
|
||||
0xd011: 'KBDCR',
|
||||
0xd012: 'DSP',
|
||||
0xd013: 'DSPCR',
|
||||
|
||||
0xff1f: "GETLINE",
|
||||
0xffef: "ECHO",
|
||||
0xffdc: "PRBYTE",
|
||||
0xffe5: "PRHEX",
|
||||
0xff1f: 'GETLINE',
|
||||
0xffef: 'ECHO',
|
||||
0xffdc: 'PRBYTE',
|
||||
0xffe5: 'PRHEX',
|
||||
};
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import CPU6502 from "js/cpu6502";
|
||||
import { debug, toHex } from "../util";
|
||||
import Apple1IO from "js/apple1io";
|
||||
import { TextPage } from "js/canvas1";
|
||||
import { byte } from "js/types";
|
||||
import CPU6502 from 'js/cpu6502';
|
||||
import { debug, toHex } from '../util';
|
||||
import Apple1IO from 'js/apple1io';
|
||||
import { TextPage } from 'js/canvas1';
|
||||
import { byte } from 'js/types';
|
||||
|
||||
// keycode: [plain, cntl, shift]
|
||||
|
||||
|
@ -140,29 +140,29 @@ const keymap: Record<byte, readonly byte[]> = {
|
|||
|
||||
const keys = [
|
||||
[
|
||||
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ":", "-", "RESET"],
|
||||
["ESC", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "FEED", "RETURN"],
|
||||
["CTRL", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "OUT", "CLS"],
|
||||
["SHIFT", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "SHIFT"],
|
||||
[" "],
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ':', '-', 'RESET'],
|
||||
['ESC', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'FEED', 'RETURN'],
|
||||
['CTRL', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', 'OUT', 'CLS'],
|
||||
['SHIFT', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'SHIFT'],
|
||||
[' '],
|
||||
],
|
||||
[
|
||||
["!", '"', "#", "$", "%", "&", "'", "(", ")", "0", "*", "=", "RESET"],
|
||||
["ESC", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "@", "LINE", "RETURN"],
|
||||
["CTRL", "A", "S", "D", "F", "BELL", "H", "J", "K", "L", "+", "RUB", "CLS"],
|
||||
["SHIFT", "Z", "X", "C", "V", "B", "^", "M", "<", ">", "?", "SHIFT"],
|
||||
[" "],
|
||||
['!', '"', '#', '$', '%', '&', "'", '(', ')', '0', '*', '=', 'RESET'],
|
||||
['ESC', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', '@', 'LINE', 'RETURN'],
|
||||
['CTRL', 'A', 'S', 'D', 'F', 'BELL', 'H', 'J', 'K', 'L', '+', 'RUB', 'CLS'],
|
||||
['SHIFT', 'Z', 'X', 'C', 'V', 'B', '^', 'M', '<', '>', '?', 'SHIFT'],
|
||||
[' '],
|
||||
],
|
||||
] as const;
|
||||
|
||||
export function mapKeyEvent(evt: KeyboardEvent) {
|
||||
var code = evt.keyCode;
|
||||
const code = evt.keyCode;
|
||||
|
||||
if (code in keymap) {
|
||||
return keymap[code][evt.shiftKey ? 2 : evt.ctrlKey ? 1 : 0];
|
||||
}
|
||||
|
||||
debug("Unhandled key = " + toHex(code));
|
||||
debug('Unhandled key = ' + toHex(code));
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
|
@ -182,76 +182,74 @@ export class KeyBoard {
|
|||
|
||||
shiftKey(down: boolean) {
|
||||
this.shifted = down;
|
||||
this.kb.querySelectorAll(".key-SHIFT").forEach(function (el) {
|
||||
this.kb.querySelectorAll('.key-SHIFT').forEach(function (el) {
|
||||
if (down) {
|
||||
el.classList.add("active");
|
||||
el.classList.add('active');
|
||||
} else {
|
||||
el.classList.remove("active");
|
||||
el.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
controlKey(down: boolean) {
|
||||
this.controlled = down;
|
||||
this.kb.querySelectorAll(".key-CTRL").forEach(function (el) {
|
||||
this.kb.querySelectorAll('.key-CTRL').forEach(function (el) {
|
||||
if (down) {
|
||||
el.classList.add("active");
|
||||
el.classList.add('active');
|
||||
} else {
|
||||
el.classList.remove("active");
|
||||
el.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
var x, y, row, key, key1, key2, label, label1, label2;
|
||||
let x, y, row, key, key1, key2, label, label1, label2;
|
||||
|
||||
function buildLabel(k: string) {
|
||||
var span = document.createElement("span");
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = k;
|
||||
if (k.length > 1 && !k.startsWith("&")) {
|
||||
span.classList.add("small");
|
||||
if (k.length > 1 && !k.startsWith('&')) {
|
||||
span.classList.add('small');
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
const _mouseup = (
|
||||
event: MouseEvent & {
|
||||
currentTarget: HTMLElement;
|
||||
const _mouseup = (event: Event) => {
|
||||
if (!(event.currentTarget instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
) => {
|
||||
event.currentTarget.classList.remove("pressed");
|
||||
event.currentTarget.classList.remove('pressed');
|
||||
};
|
||||
|
||||
const _mousedown = (
|
||||
event: MouseEvent & {
|
||||
currentTarget: HTMLElement;
|
||||
const _mousedown = (event: Event) => {
|
||||
if (!(event.currentTarget instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
) => {
|
||||
event.currentTarget.classList.add("pressed");
|
||||
var key = event.currentTarget.dataset[this.shifted ? "key2" : "key1"];
|
||||
event.currentTarget.classList.add('pressed');
|
||||
let key = event.currentTarget.dataset[this.shifted ? 'key2' : 'key1'];
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
switch (key) {
|
||||
case "BELL":
|
||||
key = "G";
|
||||
case 'BELL':
|
||||
key = 'G';
|
||||
break;
|
||||
case "RETURN":
|
||||
key = "\r";
|
||||
case 'RETURN':
|
||||
key = '\r';
|
||||
break;
|
||||
case "LINE":
|
||||
case "FEED":
|
||||
key = "\n";
|
||||
case 'LINE':
|
||||
case 'FEED':
|
||||
key = '\n';
|
||||
break;
|
||||
case "RUB":
|
||||
case "OUT":
|
||||
key = "_"; // 0x5f
|
||||
case 'RUB':
|
||||
case 'OUT':
|
||||
key = '_'; // 0x5f
|
||||
break;
|
||||
case " ":
|
||||
key = " ";
|
||||
case ' ':
|
||||
key = ' ';
|
||||
break;
|
||||
case "ESC":
|
||||
key = "\0x1b";
|
||||
case 'ESC':
|
||||
key = '\0x1b';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -259,31 +257,31 @@ export class KeyBoard {
|
|||
|
||||
if (key.length > 1) {
|
||||
switch (key) {
|
||||
case "SHIFT":
|
||||
case 'SHIFT':
|
||||
this.shifted = !this.shifted;
|
||||
this.kb
|
||||
.querySelectorAll(".key-SHIFT")
|
||||
.querySelectorAll<HTMLElement>('.key-SHIFT')
|
||||
.forEach(function (el: HTMLElement) {
|
||||
el.classList.toggle("active");
|
||||
el.classList.toggle('active');
|
||||
});
|
||||
break;
|
||||
case "CTRL":
|
||||
case 'CTRL':
|
||||
this.controlled = !this.controlled;
|
||||
this.kb.querySelectorAll(".key-CTRL").forEach(function (el) {
|
||||
el.classList.toggle("active");
|
||||
this.kb.querySelectorAll('.key-CTRL').forEach(function (el) {
|
||||
el.classList.toggle('active');
|
||||
});
|
||||
break;
|
||||
case "RESET":
|
||||
case 'RESET':
|
||||
this.cpu.reset();
|
||||
break;
|
||||
case "CLS":
|
||||
case 'CLS':
|
||||
this.text.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (this.controlled && key >= "@" && key <= "_") {
|
||||
if (this.controlled && key >= '@' && key <= '_') {
|
||||
this.io.keyDown(key.charCodeAt(0) - 0x40);
|
||||
} else {
|
||||
this.io.keyDown(key.charCodeAt(0));
|
||||
|
@ -292,46 +290,46 @@ export class KeyBoard {
|
|||
};
|
||||
|
||||
for (y = 0; y < 5; y++) {
|
||||
row = document.createElement("div");
|
||||
row.classList.add("row", "row" + y);
|
||||
row = document.createElement('div');
|
||||
row.classList.add('row', 'row' + y);
|
||||
this.kb.append(row);
|
||||
for (x = 0; x < keys[0][y].length; x++) {
|
||||
key1 = keys[0][y][x];
|
||||
key2 = keys[1][y][x];
|
||||
|
||||
label = document.createElement("div");
|
||||
label = document.createElement('div');
|
||||
label1 = buildLabel(key1);
|
||||
label2 = buildLabel(key2);
|
||||
|
||||
key = document.createElement("div");
|
||||
key.classList.add("key", "key-" + key1.replace(/[&;]/g, ""));
|
||||
key = document.createElement('div');
|
||||
key.classList.add('key', 'key-' + key1.replace(/[&;]/g, ''));
|
||||
|
||||
if (key1.length > 1) {
|
||||
if (key1 != key2) {
|
||||
key.classList.add("vcenter2");
|
||||
if (key1 !== key2) {
|
||||
key.classList.add('vcenter2');
|
||||
} else {
|
||||
key.classList.add("vcenter");
|
||||
key.classList.add('vcenter');
|
||||
}
|
||||
}
|
||||
|
||||
if (key1 != key2) {
|
||||
key.classList.add("key-" + key2.replace(/[&;]/g, ""));
|
||||
if (key1 !== key2) {
|
||||
key.classList.add('key-' + key2.replace(/[&;]/g, ''));
|
||||
label.append(label2);
|
||||
label.append(document.createElement("br"));
|
||||
label.append(document.createElement('br'));
|
||||
}
|
||||
label.append(label1);
|
||||
key.append(label);
|
||||
key.dataset["key1"] = key1;
|
||||
key.dataset["key2"] = key2;
|
||||
key.dataset['key1'] = key1;
|
||||
key.dataset['key2'] = key2;
|
||||
|
||||
if (window.ontouchstart === undefined) {
|
||||
key.addEventListener("mousedown", _mousedown);
|
||||
key.addEventListener("mouseup", _mouseup);
|
||||
key.addEventListener("mouseout", _mouseup);
|
||||
key.addEventListener('mousedown', _mousedown);
|
||||
key.addEventListener('mouseup', _mouseup);
|
||||
key.addEventListener('mouseout', _mouseup);
|
||||
} else {
|
||||
key.addEventListener("touchstart", _mousedown);
|
||||
key.addEventListener("touchend", _mouseup);
|
||||
key.addEventListener("touchleave", _mouseup);
|
||||
key.addEventListener('touchstart', _mousedown);
|
||||
key.addEventListener('touchend', _mouseup);
|
||||
key.addEventListener('touchleave', _mouseup);
|
||||
}
|
||||
|
||||
row.append(key);
|
||||
|
|
22
js/util.ts
22
js/util.ts
|
@ -9,13 +9,13 @@
|
|||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { byte, word } from "./types";
|
||||
import { byte, word } from './types';
|
||||
|
||||
var hex_digits = "0123456789ABCDEF";
|
||||
var bin_digits = "01";
|
||||
const hex_digits = '0123456789ABCDEF';
|
||||
const bin_digits = '01';
|
||||
|
||||
export function allocMem(size: word) {
|
||||
var result;
|
||||
let result;
|
||||
if (window.Uint8Array) {
|
||||
result = new Uint8Array(size);
|
||||
} else {
|
||||
|
@ -37,8 +37,8 @@ export function toHex(v: byte, n?: 2 | 4) {
|
|||
if (!n) {
|
||||
n = v < 256 ? 2 : 4;
|
||||
}
|
||||
var result = "";
|
||||
for (var idx = 0; idx < n; idx++) {
|
||||
let result = '';
|
||||
for (let idx = 0; idx < n; idx++) {
|
||||
result = hex_digits[v & 0x0f] + result;
|
||||
v >>= 4;
|
||||
}
|
||||
|
@ -46,8 +46,8 @@ export function toHex(v: byte, n?: 2 | 4) {
|
|||
}
|
||||
|
||||
export function toBinary(v: byte) {
|
||||
var result = "";
|
||||
for (var idx = 0; idx < 8; idx++) {
|
||||
let result = '';
|
||||
for (let idx = 0; idx < 8; idx++) {
|
||||
result = bin_digits[v & 0x01] + result;
|
||||
v >>= 1;
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ export function gup(name: string) {
|
|||
}
|
||||
|
||||
export function hup() {
|
||||
var regex = new RegExp("#(.*)");
|
||||
var results = regex.exec(window.location.hash);
|
||||
if (!results) return "";
|
||||
const regex = new RegExp('#(.*)');
|
||||
const results = regex.exec(window.location.hash);
|
||||
if (!results) return '';
|
||||
else return decodeURIComponent(results[1]);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -29,11 +29,14 @@
|
|||
"@babel/core": "^7.4.0",
|
||||
"@babel/preset-env": "^7.4.2",
|
||||
"@types/micromodal": "^0.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"ajv": "^6.9.2",
|
||||
"babel-jest": "^26.3.0",
|
||||
"babel-jest": "^29.5.0",
|
||||
"eslint": "^8.3.0",
|
||||
"jest": "^27.3.1",
|
||||
"eslint-plugin-jest": "^27.2.3",
|
||||
"jest": "^29.5.0",
|
||||
"node-forge": "^1.3.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.64.4",
|
||||
|
@ -41,6 +44,7 @@
|
|||
"webpack-dev-server": "^4.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"micromodal": "^0.4.9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
import CPU6502 from '../js/cpu6502';
|
||||
import Test6502 from './roms/6502test';
|
||||
import Test65C02 from './roms/65C02test';
|
||||
|
||||
import { toHex } from '../js/util';
|
||||
|
||||
describe.skip('CPU', function () {
|
||||
var cpu;
|
||||
var lastPC = 0;
|
||||
var done = false;
|
||||
|
||||
function traceCB() {
|
||||
var pc = cpu.getPC();
|
||||
done = lastPC == pc;
|
||||
lastPC = pc;
|
||||
}
|
||||
|
||||
describe('6502', function () {
|
||||
it('completes the test ROM', function () {
|
||||
cpu = new CPU6502();
|
||||
var test = new Test6502();
|
||||
cpu.addPageHandler(test);
|
||||
cpu.setPC(0x400);
|
||||
|
||||
do {
|
||||
cpu.stepCyclesDebug(1000, traceCB);
|
||||
} while (!done);
|
||||
|
||||
expect(toHex(lastPC)).toEqual(toHex(0x3469));
|
||||
});
|
||||
});
|
||||
|
||||
describe('65C02', function () {
|
||||
it('completes the test ROM', function () {
|
||||
cpu = new CPU6502({'65C02': true});
|
||||
var test = new Test65C02();
|
||||
cpu.addPageHandler(test);
|
||||
cpu.setPC(0x400);
|
||||
|
||||
do {
|
||||
cpu.stepCyclesDebug(1000, traceCB);
|
||||
} while (!done);
|
||||
|
||||
expect(toHex(lastPC)).toEqual(toHex(0x24f1));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import CPU6502, { FLAVOR_ROCKWELL_65C02 } from '../js/cpu6502';
|
||||
// From https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||
import Test6502 from './roms/6502test';
|
||||
import Test65C02 from './roms/65C02test';
|
||||
|
||||
import { toHex } from '../js/util';
|
||||
|
||||
describe('CPU', function () {
|
||||
let cpu: CPU6502;
|
||||
let lastPC = 0;
|
||||
let done = false;
|
||||
|
||||
function traceCB() {
|
||||
const pc = cpu.getPC();
|
||||
done = lastPC === pc;
|
||||
lastPC = pc;
|
||||
}
|
||||
|
||||
describe('6502', function () {
|
||||
it('completes the test ROM', function () {
|
||||
cpu = new CPU6502();
|
||||
const test = new Test6502();
|
||||
cpu.addPageHandler(test);
|
||||
cpu.setPC(0x400);
|
||||
|
||||
do {
|
||||
cpu.stepCyclesDebug(1000, traceCB);
|
||||
} while (!done);
|
||||
|
||||
expect(toHex(lastPC)).toEqual(toHex(0x3469));
|
||||
});
|
||||
});
|
||||
|
||||
describe('65C02', function () {
|
||||
it('completes the test ROM', function () {
|
||||
cpu = new CPU6502({ flavor: FLAVOR_ROCKWELL_65C02 });
|
||||
const test = new Test65C02();
|
||||
cpu.addPageHandler(test);
|
||||
cpu.setPC(0x400);
|
||||
|
||||
do {
|
||||
cpu.stepCyclesDebug(1000, traceCB);
|
||||
} while (!done);
|
||||
|
||||
expect(toHex(lastPC)).toEqual(toHex(0x24f1));
|
||||
});
|
||||
});
|
||||
});
|
2240
test/cpu6502.spec.js
2240
test/cpu6502.spec.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
// From https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { MemoryPages, byte } from '../../js/types';
|
||||
|
||||
export default class Test6502 implements MemoryPages {
|
||||
private data: Buffer;
|
||||
|
||||
constructor() {
|
||||
this.data = fs.readFileSync(path.join(__dirname, '6502_functional_test.bin'));
|
||||
}
|
||||
|
||||
start = () => {
|
||||
return 0x00;
|
||||
};
|
||||
|
||||
end = () => {
|
||||
return 0xff;
|
||||
};
|
||||
|
||||
read = (page: byte, off: byte) => {
|
||||
return this.data[page << 8 | off];
|
||||
};
|
||||
|
||||
write = (page: byte, off: byte, val: byte) => {
|
||||
this.data[page << 8 | off] = val;
|
||||
};
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
// From https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { MemoryPages, byte } from '../../js/types';
|
||||
|
||||
export default class Test65C02 implements MemoryPages {
|
||||
private data: Buffer;
|
||||
|
||||
constructor() {
|
||||
this.data = fs.readFileSync(path.join(__dirname, '65C02_extended_opcodes_test.bin'));
|
||||
}
|
||||
|
||||
start = () => {
|
||||
return 0x00;
|
||||
};
|
||||
|
||||
end = () => {
|
||||
return 0xff;
|
||||
};
|
||||
|
||||
read = (page: byte, off: byte) => {
|
||||
return this.data[page << 8 | off];
|
||||
};
|
||||
|
||||
write = (page: byte, off: byte, val: byte) => {
|
||||
this.data[page << 8 | off] = val;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { byte } from "../../js/types";
|
||||
|
||||
export const assertByte = (b: byte) => {
|
||||
expect(b <= 0xff).toEqual(true);
|
||||
expect(b >= 0x00).toEqual(true);
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
import { MemoryPages, byte } from "../../js/types";
|
||||
import { assertByte } from "./asserts";
|
||||
|
||||
export class Program implements MemoryPages {
|
||||
private data: Buffer;
|
||||
|
||||
constructor(private page: byte, code: byte[]) {
|
||||
this.data = Buffer.from(code);
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.page;
|
||||
}
|
||||
|
||||
end() {
|
||||
return this.page;
|
||||
}
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
assertByte(page);
|
||||
assertByte(off);
|
||||
return this.data[off];
|
||||
}
|
||||
|
||||
write(_page: byte, _off: byte, _val: byte) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export const bios = new Program(
|
||||
0xff,
|
||||
[
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x45, 0x4c, 0x4c,
|
||||
0x4f, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0x00, 0x04, 0x00, 0xff,
|
||||
]
|
||||
);
|
|
@ -0,0 +1,34 @@
|
|||
import { flags, CpuState } from "js/cpu6502";
|
||||
import { byte } from "js/types";
|
||||
import { toHex } from "js/util";
|
||||
|
||||
export const dumpStatusRegister = (sr: byte) =>
|
||||
[
|
||||
sr & flags.N ? "N" : "-",
|
||||
sr & flags.V ? "V" : "-",
|
||||
sr & flags.X ? "X" : "-",
|
||||
sr & flags.B ? "B" : "-",
|
||||
sr & flags.D ? "D" : "-",
|
||||
sr & flags.I ? "I" : "-",
|
||||
sr & flags.Z ? "Z" : "-",
|
||||
sr & flags.C ? "C" : "-",
|
||||
].join("");
|
||||
|
||||
const detail = !!process.env.JEST_DETAIL;
|
||||
|
||||
export function toReadableState(state: CpuState) {
|
||||
if (detail) {
|
||||
const { pc, sp, a, x, y, s } = state;
|
||||
|
||||
return {
|
||||
pc: toHex(pc, 4),
|
||||
sp: toHex(sp),
|
||||
a: toHex(a),
|
||||
x: toHex(x),
|
||||
y: toHex(y),
|
||||
s: dumpStatusRegister(s),
|
||||
};
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { MemoryPages, byte, word } from "js/types";
|
||||
import { assertByte } from "./asserts";
|
||||
|
||||
export type Log = [address: word, value: byte, types: "read" | "write"];
|
||||
export class TestMemory implements MemoryPages {
|
||||
private data: Buffer;
|
||||
private logging: boolean = false;
|
||||
private log: Log[] = [];
|
||||
|
||||
constructor(private size: number) {
|
||||
this.data = Buffer.alloc(size << 8);
|
||||
}
|
||||
|
||||
start() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
end() {
|
||||
return this.size - 1;
|
||||
}
|
||||
|
||||
read(page: byte, off: byte) {
|
||||
assertByte(page);
|
||||
assertByte(off);
|
||||
|
||||
const val = this.data[(page << 8) | off];
|
||||
if (this.logging) {
|
||||
this.log.push([(page << 8) | off, val, "read"]);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
write(page: byte, off: byte, val: byte) {
|
||||
assertByte(page);
|
||||
assertByte(off);
|
||||
assertByte(val);
|
||||
|
||||
if (this.logging) {
|
||||
this.log.push([(page << 8) | off, val, "write"]);
|
||||
}
|
||||
this.data[(page << 8) | off] = val;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.log = [];
|
||||
}
|
||||
|
||||
logStart() {
|
||||
this.log = [];
|
||||
this.logging = true;
|
||||
}
|
||||
|
||||
logStop() {
|
||||
this.logging = false;
|
||||
}
|
||||
|
||||
getLog() {
|
||||
return this.log;
|
||||
}
|
||||
}
|
|
@ -8,15 +8,13 @@
|
|||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es6",
|
||||
"lib": ["DOM", "ES6"],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"allowJs": true,
|
||||
|
|
|
@ -9,9 +9,6 @@ module.exports =
|
|||
},
|
||||
output: {
|
||||
path: path.resolve('dist/'),
|
||||
library: 'Apple1',
|
||||
libraryExport: 'Apple1',
|
||||
libraryTarget: 'var'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
|
Loading…
Reference in New Issue