Compare commits

...

43 Commits

Author SHA1 Message Date
Steven Hugg d733a2cd27 arm32: fixed VCVT, VLDR/VSTR 2024-01-09 15:56:10 -05:00
Steven Hugg 098dcda93a arm32: parse dwarf info tree, convert to number 2024-01-06 11:55:59 -05:00
Steven Hugg 886e19611e arm32: crt0 update for malloc 2024-01-05 12:23:54 -05:00
Steven Hugg 02986aed38 arm32: parse dwarf 2024-01-04 22:30:53 -05:00
Steven Hugg d5b5734ef9 arm: fixed fpu, raster frame sync 2024-01-02 13:49:55 -05:00
Steven Hugg 579e58e966 close button for debug info 2024-01-01 15:27:42 -05:00
Steven Hugg 47a7aa5a83 arm: started on fpu insns 2024-01-01 15:21:52 -05:00
Steven Hugg 9142328468 arm: merged libc and libtcc1 2023-12-31 14:45:51 -05:00
Steven Hugg 70eee2bdae arm: fixing libc 2023-12-30 16:08:16 -05:00
Steven Hugg 949e216c69 make sure display + serial is updated if advanceFrame() throws 2023-12-30 15:24:57 -05:00
Steven Hugg b160fb2ef2 license: clarified multi-license 2023-12-30 15:07:29 -05:00
Steven Hugg 63ee25741b arm: libc 2023-12-30 11:53:56 -05:00
Steven Hugg 8bdbae36e3 arm: parse ELF 2023-12-29 20:41:40 -05:00
Steven Hugg c0909bef1b arm: arm-tcc 2023-12-28 17:26:44 -05:00
Steven Hugg 8023d56b88 c64: presets 2023-12-25 10:24:31 -05:00
Steven Hugg 45bc17d7ee exidy: started platform, c example 2023-12-15 19:46:06 -05:00
Steven Hugg 8452fe73a1 galaxian: update skeleton 2023-12-15 10:18:04 -05:00
Steven Hugg dff5c73d6a directive #embed for sdcc and cmoc 2023-12-13 23:38:38 -05:00
Steven Hugg f8462de014 williams: updated presets 2023-12-13 21:27:54 -05:00
Steven Hugg d08d73f422 astrocade: worked on arcade emulation 2023-12-13 20:54:42 -05:00
Steven Hugg 0ede0e514b sdcc: link errors no longer vanish if compile errors occur 2023-12-13 13:35:01 -05:00
Steven Hugg dbe73c4fb2 verilog: added example categories 2023-12-04 13:20:35 -06:00
Steven Hugg 9ecfb3cfa8 ide: refactored ui.ts a bit 2023-12-04 11:59:51 -06:00
Steven Hugg 312cb3d025 c64: removed wait_vblank macro 2023-12-01 11:14:18 -06:00
Steven Hugg bd63ef1268 dasm: started testing wasi
dasm:
2023-11-30 12:04:05 -06:00
Steven Hugg 2e0382b0a6 worker: refactor 2023-11-29 15:04:05 -06:00
Steven Hugg 611c174aed c64: set_raster_irq(), wait_vblank() 2023-11-29 12:51:42 -06:00
Steven Hugg d9001df5d4 cc7800 2023-11-28 18:08:02 -06:00
Steven Hugg e6c3dc98e1 fixed tests 2023-11-25 13:18:41 -06:00
Steven Hugg 43d844fa79 cleaned up Memory Map view
apple2: no logIOWrite for reads
2023-11-25 12:33:08 -06:00
Steven Hugg c5bcd8ad9b npm audit fix 2023-11-23 14:12:30 -06:00
Steven Hugg c189875be3 apple2: AppleSingle header for CC65 by default, try to parse 4-byte DOS headers 2023-11-22 21:43:30 -06:00
Steven Hugg 16fcf33881 c64: presets 2023-11-20 18:44:26 -06:00
Steven Hugg 8091985fde wasi: working on shim 2023-11-20 13:45:22 -06:00
Steven Hugg c9354a83ea changed #incbin to #embed (loosely based on C23 standard) 2023-11-17 10:29:35 -06:00
Steven Hugg 73c7ac5941 added category field to presets 2023-11-16 13:40:23 -06:00
Steven Hugg 19e3bbbea3 c64: updated examples 2023-11-14 15:24:49 -06:00
Steven Hugg 12957d7740 changed file selector to <optgroup> 2023-11-14 10:57:37 -06:00
Steven Hugg a252ea65bd vic20: default tiny bios 2023-11-13 22:35:49 -06:00
Steven Hugg 1c0b3e2fdd vcs: fixed test with null Context2D 2023-11-13 16:10:07 -06:00
Steven Hugg 44271fe9b8 added acme assembler
vcslib: increased # of lines in kernel
2023-11-13 12:37:38 -06:00
Steven Hugg f6452a719f c64: update presets, refactor analysis 2023-11-11 22:01:39 -06:00
Steven Hugg de6250b0cd show raster x/y crosshair when stopped (getRasterCanvasPosition) 2023-11-10 12:56:16 -06:00
133 changed files with 10877 additions and 3378 deletions

View File

@ -17,9 +17,9 @@ jobs:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

View File

@ -45,20 +45,22 @@ Note: Github tests may fail due to lack of API key.
## License
Copyright © 2016-2022 [Steven Hugg](https://github.com/sehugg).
Copyright © 2016-2024 [Steven E. Hugg](https://github.com/sehugg).
This project is [GPL-3.0](https://github.com/sehugg/8bitworkshop/blob/master/LICENSE) licensed.
Dependencies retain their original licenses.
This project, unless specifically noted, is multi-licensed.
You may choose to adhere to the terms of either the [GPL-3.0](https://github.com/sehugg/8bitworkshop/blob/master/LICENSE) License for the entire project or respect the individual licenses of its dependencies and included code samples, as applicable.
All included code samples (all files under the presets/ directory) are licensed under
This project includes various dependencies, modules, and components that retain their original licenses.
For detailed licensing information for each dependency, please refer to the respective files and documentation.
All included code samples located in the presets/ directory are licensed under
[CC0](https://creativecommons.org/publicdomain/zero/1.0/)
unless otherwise licensed.
unless a different license is explicitly stated within the specific code sample.
## Dependencies
The IDE uses custom forks for many of these, found at https://github.com/sehugg?tab=repositories
### Emulators
* https://javatari.org/
@ -83,6 +85,8 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg?
* https://github.com/dmsc/fastbasic
* https://github.com/wiz-lang/wiz
* https://github.com/sylefeb/Silice
* https://github.com/steux/cc7800
* https://bellard.org/tcc/
### Assemblers/Linkers
@ -111,6 +115,8 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg?
* https://github.com/sehugg/8bitworkshop-compilers
* https://github.com/sehugg/8bit-tools
* https://github.com/sehugg/awesome-8bitgamedev
* https://github.com/sehugg?tab=repositories
## Tool Server (experimental)

View File

@ -127,13 +127,15 @@ div.mem_info {
bottom: 10px;
background-color: #333;
color: #66ff66;
white-space: pre;
padding: 20px;
z-index: 12;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 12pt;
box-shadow: 0px 0px 8px rgba(0,0,0,.5);
max-height: 90vh;
}
div.mem_info_msg {
white-space: pre;
padding: 20px;
max-height: 80vh;
overflow-y: auto;
}
div.mem_info a {
@ -148,6 +150,9 @@ div.mem_info a:hover {
div.mem_info a.selected {
color: #ffffff;
}
div.mem_info button {
color: #fff;
}
.mem_info_links {
text-align:right;
}

View File

@ -167,7 +167,6 @@ body {
<li><a class="dropdown-item" href="?platform=msx-libcv">MSX (libCV)</a></li>
<li><a class="dropdown-item" href="?platform=apple2">Apple ][+</a></li>
<li><a class="dropdown-item" href="?platform=zx">ZX Spectrum</a></li>
<li><a class="dropdown-item" href="?platform=x86">x86 (FreeDOS)</a></li>
<li><a class="dropdown-item" href="?platform=cpc.6128">Amstrad CPC6128</a></li>
</ul>
</li>
@ -209,6 +208,7 @@ body {
<li><a class="dropdown-item" href="?platform=nes.mame">NES (MAME)</a></li>
<hr>
<li><a class="dropdown-item" href="?platform=williams">Williams (6809)</a></li>
<li><a class="dropdown-item" href="?platform=x86">x86 (FreeDOS)</a></li>
</ul>
</li>
</ul>
@ -378,6 +378,8 @@ body {
</div>
</div>
<div id="mem_info" class="mem_info" style="display:none">
<div><button type="button" class="close" onclick="$('.mem_info').hide()" aria-hidden="true">&times;</button></div>
<div id="mem_info_msg" class="mem_info_msg"></div>
</div>
<div id="error_alert" class="alert alert-danger alert-dismissable" style="position:absolute;right:0;top:0;display:none">
<button type="button" class="close" onclick="$('.alert').hide()" aria-hidden="true">&times;</button>

@ -1 +1 @@
Subproject commit 9d124f087e1f0c7f74f9244b9679cc62e71fa524
Subproject commit 113cd5741e5c414bbbe47ef8be7a896652d48f64

2873
package-lock.json generated

File diff suppressed because it is too large Load Diff

55
presets/apple2/lz4test.c Normal file
View File

@ -0,0 +1,55 @@
/*
Test of the LZ4 decompression library
with a hires graphics image.
*/
// CC65 config, reserves space for the HGR1 screen buffer
#define CFGFILE apple2-hgr.cfg
#pragma data-name(push,"HGR")
// this segment is required, but we leave it empty
// since we're going to decompress the image here
#pragma data-name(pop)
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <conio.h>
#include <string.h>
#include <apple2.h>
#include <peekpoke.h>
#include <lz4.h>
// STROBE = write any value to an I/O address
#define STROBE(addr) __asm__ ("sta %w", addr)
// start address of the two hi-res graphics regions
#define HGR1 0x2000
#define HGR2 0x4000
// the LZ4 compressed data
const unsigned char BITMAP_DATA_LZ4[] = {
#embed "parrot-apple2.hires.lz4"
};
// clear screen and set graphics mode
void clear_hgr1() {
memset((char*)HGR1, 0, 0x2000); // clear page 1
STROBE(0xc052); // turn off mixed-mode
STROBE(0xc054); // page 1
STROBE(0xc057); // hi-res
STROBE(0xc050); // set graphics mode
}
int main (void)
{
// set hgr1 mode and clear
clear_hgr1();
// skip the header (usually 11 bytes)
decompress_lz4(BITMAP_DATA_LZ4+11, (char*)HGR1, 0x2000);
// wait for a key
cgetc();
return EXIT_SUCCESS;
}

Binary file not shown.

View File

@ -0,0 +1,7 @@
#define SERIAL_OUT ((int*)0x4000048)
void putchar_(char c) {
*SERIAL_OUT = c;
}

35
presets/arm32/sieve.c Normal file
View File

@ -0,0 +1,35 @@
#include <string.h>
#include <stdio.h>
#define true 1
#define false 0
#define size 8190
#define sizepl 8191
//#link "serialout.c"
main() {
char flags[sizepl];
int i, prime, k, count, iter;
printf("Running benchmark...\n");
for (iter = 1; iter <= 10; iter ++) {
count=0;
for (i = 0; i <= size; i++)
flags[i] = true;
for (i = 0; i <= size; i++) {
if (flags[i]) {
prime = i + i + 3;
k = i + prime;
while (k <= size) {
flags[k] = false;
k += prime;
}
count = count + 1;
}
}
}
printf("Primes: %d\n", count);
return 0;
}

View File

@ -0,0 +1,11 @@
#include <string.h>
#include <stdio.h>
//#link "serialout.c"
int main() {
int x = 2024;
printf("Hello World! %d\n", x);
return 0;
}

25
presets/arm32/vidfill.c Normal file
View File

@ -0,0 +1,25 @@
const char const str[] = "HELLO WORLD!";
int global = 0x1234;
int global2 = 0x123456;
#define VIDBASE ((int*)0x4000080)
int vidbuf[160*128];
int main() {
*VIDBASE = (int)vidbuf;
global += str[0];
global++;
global2++;
int c = 0xff880000;
c += str[0];
int* p = (int*) vidbuf;
for (int i=0; i<160*128; i++) {
p[i] = c++;
}
return 0;
}

View File

@ -0,0 +1,59 @@
// example from https://github.com/steux/cc7800 - license: GPLv3
#include "prosystem.h"
#include "multisprite.h"
char i, xpos, ypos;
#define NB_SMALL_SPRITES 128
ramchip short sp_xpos[NB_SMALL_SPRITES], sp_ypos[NB_SMALL_SPRITES];
ramchip char sp_direction[NB_SMALL_SPRITES];
const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
// Generated with sprites7800 missile.yaml
holeydma reversed scattered(16,1) char missile[16] = {
0x18, 0x96, 0x7a, 0x7e, 0x7e, 0x6e, 0x9a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void main()
{
multisprite_init();
*P0C1 = multisprite_color(0x1c); // Yellow
*P0C2 = multisprite_color(0x37); // Orange
*P0C3 = multisprite_color(0x43); // Red
// Initialize small sprites
for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SMALL_SPRITES; xpos++, ypos++, X++) {
sp_xpos[X] = xpos << 8;
sp_ypos[X] = ypos << 8;
sp_direction[X] = i++;
if (i == 24) i = 0;
}
// Main loop
do {
multisprite_flip();
for (i = 0; i != NB_SMALL_SPRITES; i++) {
X = i;
Y = sp_direction[X];
sp_xpos[X] += dx[Y];
sp_ypos[X] += dy[Y];
xpos = sp_xpos[X] >> 8;
ypos = sp_ypos[X] >> 8;
if ((xpos < 5 && (dx[Y] >> 8) < 0) ||
(xpos >= 150 && (dx[Y] >> 8) >= 0)) {
sp_direction[X] = horizontal_pingpong[Y];
}
if ((ypos < 5 && (dy[Y] >> 8) < 0) ||
(ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
sp_direction[X] = vertical_pingpong[Y];
}
multisprite_display_small_sprite(xpos, ypos, missile, 1, 0, 8);
}
} while(1);
}

View File

@ -0,0 +1,110 @@
// example from https://github.com/steux/cc7800 - license: GPLv3
#include "prosystem.h"
#include "gfx.h"
#define DMA_CHECK
#define VERTICAL_SCROLLING
#define _MS_DL_SIZE 64
#define _MS_DL_MALLOC(y) ((y == 6 || y == 7 || y == 8)?_MS_DL_SIZE * 2:_MS_DL_SIZE)
#include "multisprite.h"
char i, counter, xpos, ypos;
char *ptr;
char xchest;
#define NB_SPRITES 32
ramchip short sp_xpos[NB_SPRITES], sp_ypos[NB_SPRITES];
ramchip char sp_direction[NB_SPRITES];
const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
const char background[22] = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 };
#define LTR(x) (((x) - ' ') * 2)
const char hello_world[] = { LTR('H'), LTR('E'), LTR('L'), LTR('L'), LTR('O'), LTR(' '), LTR('W'), LTR('O'), LTR('R'), LTR('L'), LTR('D') };
void main()
{
counter = 0;
multisprite_init();
multisprite_set_charbase(tiles);
// Set up a full background
for (counter = 0; counter < _MS_DLL_ARRAY_SIZE; counter++) {
if (counter & 2) {
ptr = background + 2;
} else {
ptr = background;
}
multisprite_display_tiles(0, _MS_DLL_ARRAY_SIZE - 1 - counter, ptr, 20, 1);
}
multisprite_save();
*P0C1 = multisprite_color(0x1c); // Setup Palette 0: Yellow
*P0C2 = multisprite_color(0xc5); // Green
*P0C3 = 0x0f; // White
*P1C1 = multisprite_color(0x55); // Dark pink
*P1C2 = multisprite_color(0x5B); // Light pink
*P2C1 = multisprite_color(0x32);
*P2C2 = multisprite_color(0x3D);
*P2C3 = multisprite_color(0x37);
*P3C1 = multisprite_color(0x92);
*P3C2 = multisprite_color(0x97);
*P3C3 = multisprite_color(0x9D);
// Initialize sprites
for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SPRITES; xpos++, ypos++, X++) {
sp_xpos[X] = xpos << 8;
sp_ypos[X] = ypos << 8;
sp_direction[X] = i++;
if (i == 24) i = 0;
}
// Main loop
do {
// Prepare scrolling data
if (multisprite_vscroll_buffer_empty()) {
if (counter & 2) {
ptr = background + 2;
} else {
ptr = background;
}
multisprite_vscroll_buffer_tiles(0, ptr, 20, 1);
multisprite_vscroll_buffer_sprite(xchest, chest, 2, 3);
xchest += 45;
counter++;
}
while (*MSTAT & 0x80);
multisprite_flip();
multisprite_vertical_scrolling(-1);
multisprite_reserve_dma(104, sizeof(hello_world), 2);
for (i = 0; i != NB_SPRITES; i++) {
X = i;
Y = sp_direction[X];
sp_xpos[X] += dx[Y];
sp_ypos[X] += dy[Y];
xpos = sp_xpos[X] >> 8;
ypos = sp_ypos[X] >> 8;
if ((xpos < 5 && (dx[Y] >> 8) < 0) ||
(xpos >= 150 && (dx[Y] >> 8) >= 0)) {
sp_direction[X] = horizontal_pingpong[Y];
}
if ((ypos < 5 && (dy[Y] >> 8) < 0) ||
(ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
sp_direction[X] = vertical_pingpong[Y];
}
multisprite_display_sprite(xpos, ypos, bb_char1, 2, 0);
}
for (xpos = 40, i = 0; i != sizeof(hello_world); xpos += 8, i++) {
ptr = chars0 + hello_world[X = i];
multisprite_display_sprite_fast(xpos, 104, ptr, 2, 2);
}
} while(1);
}

152
presets/atari7800/gfx.h Normal file
View File

@ -0,0 +1,152 @@
// example from https://github.com/steux/cc7800 - license: GPLv3
reversed scattered(16,4) char tiles[64] = {
0x5a, 0x5a, 0x95, 0x95, 0x69, 0x69, 0x65, 0x65, 0x69, 0x69, 0x95, 0x95, 0xa5, 0xa5, 0x65, 0x65,
0xa5, 0xa5, 0xa9, 0xa9, 0x96, 0x96, 0xa6, 0xa6, 0x96, 0x96, 0xa9, 0xa9, 0x5a, 0x5a, 0xa6, 0xa6,
0x5a, 0x5a, 0x95, 0x95, 0x69, 0x69, 0x65, 0x65, 0x69, 0x69, 0x95, 0x95, 0xa5, 0xa5, 0x65, 0x65,
0xa5, 0xa5, 0xa9, 0xa9, 0x96, 0x96, 0xa6, 0xa6, 0x96, 0x96, 0xa9, 0xa9, 0x5a, 0x5a, 0xa6, 0xa6
};
holeydma reversed scattered(16,32) char chars0[512] = {
0x00, 0x00, 0x05, 0x00, 0x50, 0x50, 0x51, 0x40, 0x05, 0x00, 0x50, 0x10, 0x15, 0x40, 0x00, 0x50,
0x01, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0x50, 0x50, 0x51, 0x40, 0x05, 0x00, 0x50, 0x10, 0x15, 0x40, 0x00, 0x50,
0x01, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09, 0x00, 0x60, 0x90, 0x62, 0x40, 0x16, 0x60, 0x90, 0x60, 0x5f, 0x90, 0x01, 0xb0,
0x06, 0xc0, 0x36, 0x00, 0x90, 0x60, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
0x00, 0x00, 0x05, 0x00, 0x50, 0x50, 0x51, 0x80, 0x25, 0x50, 0x50, 0x50, 0x90, 0x60, 0x01, 0x40,
0x05, 0x00, 0x05, 0x00, 0x50, 0x50, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x09, 0x00, 0x50, 0x92, 0x65, 0x90, 0x6f, 0xf0, 0xf2, 0x70, 0xd9, 0x70, 0x09, 0xc0,
0x27, 0x00, 0x0e, 0x40, 0xd6, 0x70, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c,
0x00, 0x00, 0x06, 0x00, 0x90, 0x61, 0x99, 0x60, 0x50, 0x00, 0x01, 0x80, 0x26, 0x40, 0x06, 0x00,
0x18, 0x00, 0x01, 0x80, 0x25, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90,
0x00, 0x00, 0x06, 0x00, 0xf0, 0xf3, 0x6e, 0x70, 0xe6, 0x40, 0x06, 0xc0, 0x26, 0xc0, 0x0f, 0x00,
0x24, 0x00, 0x02, 0x42, 0x66, 0x64, 0x66, 0x60, 0x00, 0x00, 0x66, 0x60, 0x00, 0x00, 0x02, 0x70,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x62, 0x40, 0x26, 0x40, 0x06, 0x00, 0x26, 0x00, 0x00, 0x00,
0x24, 0x00, 0x02, 0x42, 0x66, 0x64, 0x66, 0x60, 0x00, 0x00, 0x66, 0x60, 0x00, 0x00, 0x02, 0x40,
0x00, 0x00, 0x0f, 0x00, 0x00, 0x02, 0x6a, 0x60, 0x3f, 0xa0, 0x1b, 0x00, 0x6d, 0xa0, 0x00, 0x00,
0x28, 0x00, 0x02, 0x83, 0xda, 0x7c, 0xf6, 0xf0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x09, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa6, 0x90, 0x00, 0x90, 0x28, 0x00, 0x92, 0x60, 0x00, 0x00,
0x24, 0x00, 0x01, 0x80, 0x29, 0x80, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xae, 0xb0, 0x6a, 0xb0, 0xac, 0xa0, 0xa2, 0x70, 0x00, 0x00,
0x36, 0x00, 0x0a, 0xc0, 0xaf, 0xa0, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x27, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x40, 0xa6, 0x80, 0x90, 0x60, 0x62, 0x80, 0x00, 0x00,
0x0a, 0x00, 0x06, 0x00, 0x90, 0x60, 0x09, 0x00, 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x28, 0x00,
0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xa2, 0x80, 0xfa, 0xc0, 0xb0, 0xa0, 0xea, 0xa0, 0x00, 0x00,
0x0e, 0x80, 0x2b, 0x00, 0xf0, 0xf0, 0x0f, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xac, 0x00,
0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xa2, 0x80, 0x0a, 0x00, 0x80, 0xa0, 0x2a, 0xa0, 0x00, 0x00,
0x02, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xa0, 0x00,
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0xf3, 0xc0, 0x0f, 0x00, 0xc0, 0xf0, 0x3f, 0xf0, 0x00, 0x00,
0x03, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf0, 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
};
holeydma reversed scattered(16,32) char chars1[512] = {
0x15, 0x40, 0x05, 0x00, 0x15, 0x40, 0x15, 0x40, 0x01, 0x40, 0x55, 0x50, 0x15, 0x40, 0x55, 0x50,
0x15, 0x40, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00, 0x15, 0x40,
0x15, 0x40, 0x05, 0x00, 0x15, 0x40, 0x15, 0x40, 0x01, 0x40, 0x55, 0x50, 0x15, 0x40, 0x55, 0x50,
0x15, 0x40, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00, 0x15, 0x40,
0x5a, 0x70, 0x0d, 0x00, 0x7a, 0xd0, 0x7a, 0x50, 0x07, 0x40, 0xda, 0xa0, 0x5a, 0xd0, 0x7a, 0xd0,
0x7a, 0x50, 0xda, 0x70, 0x00, 0x00, 0x00, 0x00, 0x05, 0xa0, 0x00, 0x00, 0xa7, 0x00, 0xda, 0x70,
0x70, 0xd0, 0x05, 0x00, 0x50, 0x50, 0x50, 0xd0, 0x05, 0x40, 0x50, 0x00, 0xd0, 0x70, 0x50, 0x50,
0x50, 0xd0, 0x70, 0x50, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x50, 0x50,
0x51, 0x70, 0x1d, 0x00, 0xa0, 0xd0, 0xa0, 0xd0, 0x37, 0x40, 0xd7, 0x40, 0xd0, 0xa0, 0xa1, 0xe0,
0x70, 0xd0, 0x70, 0x50, 0x07, 0x00, 0x0d, 0x00, 0x1e, 0x00, 0x75, 0xd0, 0x0b, 0x40, 0xa0, 0x70,
0xd3, 0x50, 0x37, 0x00, 0x00, 0x70, 0x00, 0x70, 0x1d, 0xc0, 0x75, 0xc0, 0x70, 0x00, 0x03, 0x40,
0xd0, 0x70, 0x50, 0xd0, 0x05, 0x00, 0x07, 0x00, 0x14, 0x00, 0xdd, 0x70, 0x01, 0xc0, 0x00, 0xd0,
0x76, 0xd0, 0x27, 0x00, 0x03, 0x60, 0x07, 0x60, 0x6b, 0x40, 0xaa, 0x70, 0x77, 0x40, 0x07, 0x80,
0xb7, 0x60, 0xb7, 0x70, 0x0a, 0x00, 0x0a, 0x00, 0x78, 0x00, 0xaa, 0xa0, 0x02, 0x70, 0x03, 0x60,
0xdc, 0x70, 0x07, 0x00, 0x03, 0x40, 0x07, 0x40, 0x43, 0x40, 0x00, 0x70, 0x77, 0x40, 0x07, 0x00,
0x37, 0x40, 0x37, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70, 0x03, 0x40,
0xd8, 0xf0, 0x0d, 0x00, 0x36, 0x80, 0x0a, 0x70, 0xf7, 0xf0, 0x00, 0x70, 0x7a, 0xf0, 0x07, 0x00,
0x7a, 0x70, 0x2a, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x00, 0xdf, 0x70, 0x03, 0xe0, 0x0d, 0x80,
0xf0, 0x70, 0x0f, 0x00, 0x1c, 0x00, 0x00, 0xd0, 0x7d, 0xd0, 0x00, 0xf0, 0xd0, 0x70, 0x0f, 0x00,
0xf0, 0xd0, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x7d, 0xf0, 0x03, 0x40, 0x07, 0x00,
0xf0, 0xd0, 0x0f, 0x00, 0xf8, 0x00, 0xf0, 0xf0, 0xab, 0xe0, 0xf0, 0xf0, 0xf0, 0x70, 0x0f, 0x00,
0xf0, 0xf0, 0x70, 0xf0, 0x0f, 0x00, 0x0f, 0x00, 0x2f, 0x00, 0xaa, 0xa0, 0x0f, 0x80, 0x0a, 0x00,
0xd0, 0xf0, 0x0f, 0x00, 0xd0, 0x00, 0xf0, 0x70, 0x03, 0xc0, 0xd0, 0x70, 0x70, 0xf0, 0x0d, 0x00,
0x70, 0x70, 0xf0, 0xd0, 0x0f, 0x00, 0x0d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
0xbd, 0xe0, 0xff, 0xf0, 0xff, 0xf0, 0xbf, 0xe0, 0x03, 0xc0, 0xbf, 0xe0, 0xbf, 0xe0, 0x0f, 0x00,
0xbf, 0xe0, 0xbf, 0xe0, 0x0a, 0x00, 0x3e, 0x00, 0x0b, 0xf0, 0x00, 0x00, 0xfe, 0x00, 0x0d, 0x00,
0x3f, 0xc0, 0xff, 0xf0, 0xff, 0xf0, 0x3f, 0xc0, 0x03, 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x0f, 0x00,
0x3f, 0xc0, 0x3f, 0xc0, 0x00, 0x00, 0x3c, 0x00, 0x03, 0xf0, 0x00, 0x00, 0xfc, 0x00, 0x0f, 0x00,
0x2a, 0x80, 0xaa, 0xa0, 0xaa, 0xa0, 0x2a, 0x80, 0x02, 0x80, 0x2a, 0x80, 0x2a, 0x80, 0x0a, 0x00,
0x2a, 0x80, 0x2a, 0x80, 0x00, 0x00, 0x28, 0x00, 0x02, 0xa0, 0x00, 0x00, 0xa8, 0x00, 0x0a, 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
};
holeydma reversed scattered(16,32) char chars2[512] = {
0x15, 0x40, 0x05, 0x00, 0x55, 0x40, 0x15, 0x40, 0x55, 0x00, 0x55, 0x50, 0x55, 0x50, 0x15, 0x40,
0x50, 0x50, 0x15, 0x40, 0x05, 0x50, 0x50, 0x50, 0x50, 0x00, 0x50, 0x14, 0x50, 0x50, 0x15, 0x40,
0x15, 0x40, 0x05, 0x00, 0x55, 0x40, 0x15, 0x40, 0x55, 0x00, 0x55, 0x50, 0x55, 0x50, 0x15, 0x40,
0x50, 0x50, 0x15, 0x40, 0x05, 0x50, 0x50, 0x50, 0x50, 0x00, 0x50, 0x14, 0x50, 0x50, 0x15, 0x40,
0x5a, 0x70, 0x1d, 0xc0, 0x7a, 0xd0, 0x7a, 0x50, 0xdb, 0x40, 0xda, 0xa0, 0x5a, 0xa0, 0x7a, 0xd0,
0x70, 0x50, 0x27, 0x80, 0x09, 0x60, 0x51, 0xe0, 0x70, 0x00, 0x74, 0x5c, 0xd4, 0x70, 0xda, 0x70,
0x70, 0xd0, 0x15, 0x40, 0x50, 0x50, 0x50, 0xd0, 0x71, 0x40, 0x50, 0x00, 0xd0, 0x00, 0x50, 0x50,
0x50, 0xd0, 0x05, 0x00, 0x03, 0x40, 0xd1, 0x40, 0x50, 0x00, 0x54, 0xd4, 0x74, 0x50, 0x50, 0x50,
0x51, 0x70, 0xda, 0x70, 0x50, 0xd0, 0x70, 0xa0, 0x72, 0x50, 0xd0, 0x00, 0xd0, 0x00, 0x50, 0xa0,
0x70, 0xd0, 0x07, 0x00, 0x03, 0x40, 0xdd, 0x80, 0x50, 0x00, 0x75, 0xdc, 0x77, 0x50, 0xd0, 0x70,
0xd3, 0x50, 0x70, 0x50, 0xd0, 0x70, 0xd0, 0x00, 0x50, 0xd0, 0x70, 0x00, 0x70, 0x00, 0xd0, 0x00,
0xd0, 0x70, 0x0d, 0x00, 0x01, 0xc0, 0x77, 0x00, 0xd0, 0x00, 0xdd, 0x74, 0x5d, 0xd0, 0x70, 0xd0,
0x73, 0xd0, 0x77, 0x70, 0x77, 0x60, 0x70, 0x00, 0x70, 0x70, 0x77, 0x00, 0x77, 0x00, 0x73, 0x70,
0x77, 0x70, 0x07, 0x00, 0x03, 0x40, 0x76, 0x00, 0x70, 0x00, 0x7b, 0xb4, 0x77, 0x70, 0x70, 0x70,
0xd1, 0x70, 0x77, 0x70, 0x77, 0x40, 0x70, 0x00, 0x70, 0x70, 0x77, 0x00, 0x77, 0x00, 0x73, 0x70,
0x77, 0x70, 0x07, 0x00, 0x03, 0x40, 0x74, 0x00, 0x70, 0x00, 0x73, 0x34, 0x77, 0x70, 0x70, 0x70,
0xd2, 0xa0, 0x7a, 0xf0, 0xfa, 0xd0, 0x70, 0x00, 0xf0, 0xf0, 0xda, 0x00, 0x7a, 0x00, 0xf2, 0xd0,
0x7a, 0x70, 0x07, 0x00, 0x03, 0x40, 0xf7, 0x00, 0x70, 0x00, 0xd2, 0x3c, 0xfb, 0xd0, 0x70, 0xf0,
0xf0, 0x00, 0xd0, 0x70, 0xd0, 0x70, 0xf0, 0x00, 0x70, 0xd0, 0x70, 0x00, 0xd0, 0x00, 0xd0, 0x70,
0xf0, 0xd0, 0x0d, 0x00, 0x01, 0xc0, 0xdf, 0x00, 0xf0, 0x00, 0x70, 0x34, 0xd3, 0x70, 0xf0, 0xd0,
0xf0, 0x10, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x73, 0xe0, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xf0,
0xf0, 0xf0, 0x0f, 0x00, 0xf3, 0xc0, 0xfb, 0xc0, 0xf0, 0x00, 0xf0, 0x3c, 0xf2, 0xf0, 0xf0, 0xf0,
0xd0, 0x30, 0xd0, 0xf0, 0xd0, 0x70, 0xf0, 0x70, 0xf3, 0xc0, 0xd0, 0x00, 0x70, 0x00, 0xd0, 0xd0,
0x70, 0x70, 0x07, 0x00, 0xd3, 0x40, 0xd1, 0xc0, 0x70, 0x00, 0xd0, 0x34, 0xd0, 0xf0, 0xf0, 0xf0,
0xbd, 0xe0, 0xf0, 0xf0, 0xff, 0xe0, 0xbf, 0xe0, 0xff, 0x80, 0xff, 0xf0, 0xf0, 0x00, 0xbf, 0xe0,
0xf0, 0xf0, 0x3f, 0xc0, 0xbf, 0x80, 0xf2, 0xf0, 0xff, 0xf0, 0xf0, 0x3c, 0xf0, 0xf0, 0xbd, 0xe0,
0x3f, 0xc0, 0xf0, 0xf0, 0xff, 0xc0, 0x3f, 0xc0, 0xff, 0x00, 0xff, 0xf0, 0xf0, 0x00, 0x3f, 0xc0,
0xf0, 0xf0, 0x3f, 0xc0, 0x3f, 0x00, 0xf0, 0xf0, 0xff, 0xf0, 0xf0, 0x3c, 0xf0, 0xf0, 0x3f, 0xc0,
0x2a, 0x80, 0xa0, 0xa0, 0xaa, 0x80, 0x2a, 0x80, 0xaa, 0x00, 0xaa, 0xa0, 0xa0, 0x00, 0x2a, 0x80,
0xa0, 0xa0, 0x2a, 0x80, 0x2a, 0x00, 0xa0, 0xa0, 0xaa, 0xa0, 0xa0, 0x28, 0xa0, 0xa0, 0x2a, 0x80,
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
};
holeydma reversed scattered(16,32) char chars3[512] = {
0x55, 0x40, 0x15, 0x40, 0x55, 0x40, 0x15, 0x40, 0x55, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x14,
0x50, 0x50, 0x50, 0x50, 0x55, 0x50, 0x15, 0x40, 0x00, 0x50, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00,
0x55, 0x40, 0x15, 0x40, 0x55, 0x40, 0x15, 0x40, 0x55, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x14,
0x50, 0x50, 0x50, 0x50, 0x55, 0x50, 0x15, 0x40, 0x00, 0x50, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00,
0x5a, 0x70, 0x5a, 0xd0, 0x7a, 0xd0, 0x7a, 0x50, 0xa7, 0xa0, 0xd0, 0x70, 0x50, 0xd0, 0x70, 0x1c,
0x70, 0x50, 0xd0, 0x70, 0xaa, 0x70, 0x1e, 0x80, 0x01, 0xac, 0x2b, 0x40, 0x07, 0x00, 0x0c, 0x00,
0x70, 0xd0, 0xd0, 0x70, 0x50, 0x50, 0x50, 0xd0, 0x05, 0x00, 0x50, 0x50, 0xd0, 0x70, 0x50, 0x14,
0x50, 0xd0, 0x70, 0x50, 0x00, 0x50, 0x14, 0x00, 0x01, 0x04, 0x01, 0xc0, 0x05, 0x00, 0x04, 0x00,
0x50, 0x70, 0xd0, 0x70, 0x50, 0xd0, 0x70, 0xa0, 0x07, 0x00, 0xd0, 0x70, 0xd0, 0x70, 0x50, 0x14,
0xb5, 0xe0, 0x70, 0x50, 0x03, 0x60, 0x1c, 0x00, 0x0d, 0x08, 0x01, 0xc0, 0x37, 0x40, 0x14, 0x00,
0xd0, 0x50, 0x70, 0x50, 0xd0, 0x70, 0xd0, 0x00, 0x0d, 0x00, 0x70, 0xd0, 0x70, 0x50, 0xd0, 0x34,
0x1d, 0x40, 0x50, 0xd0, 0x01, 0xc0, 0x34, 0x00, 0x07, 0x00, 0x01, 0x40, 0x1d, 0xc0, 0x34, 0x00,
0x77, 0xe0, 0x70, 0x70, 0x77, 0x60, 0xb7, 0x40, 0x07, 0x00, 0x70, 0x70, 0x70, 0x70, 0x73, 0x34,
0x27, 0x80, 0xb7, 0x60, 0x07, 0x80, 0x34, 0x00, 0x37, 0x70, 0x03, 0x40, 0x77, 0x70, 0x77, 0x74,
0xdd, 0x40, 0x70, 0x70, 0x77, 0x40, 0x37, 0x40, 0x07, 0x00, 0x70, 0x70, 0x70, 0x70, 0x73, 0x34,
0x07, 0x00, 0x37, 0x40, 0x07, 0x00, 0x34, 0x00, 0x37, 0x70, 0x03, 0x40, 0x77, 0x70, 0x77, 0x74,
0xda, 0x80, 0x70, 0xf0, 0xf7, 0x80, 0x2a, 0x70, 0x07, 0x00, 0xd0, 0x70, 0x70, 0xf0, 0xf7, 0xdc,
0x3f, 0x40, 0x27, 0x80, 0x1e, 0x00, 0x34, 0x00, 0x2d, 0xa0, 0x03, 0x40, 0xa7, 0xa0, 0x7d, 0xf4,
0xf0, 0x00, 0xd0, 0x70, 0xdf, 0x00, 0x00, 0xd0, 0x0d, 0x00, 0x70, 0xf0, 0xd0, 0x70, 0xdf, 0x7c,
0x37, 0xc0, 0x0d, 0x00, 0x3c, 0x00, 0x1c, 0x00, 0x07, 0x00, 0x01, 0xc0, 0x0f, 0x00, 0xf7, 0xdc,
0xf0, 0x00, 0xbf, 0x60, 0xfb, 0xc0, 0xf0, 0xf0, 0x0f, 0x00, 0xf0, 0xf0, 0xbf, 0x60, 0xfe, 0xfc,
0xfa, 0xf0, 0x0f, 0x00, 0xf8, 0x00, 0x3c, 0x00, 0x3e, 0x0c, 0x03, 0xc0, 0x0f, 0x00, 0xb6, 0xa8,
0xd0, 0x00, 0x1f, 0xc0, 0xd3, 0x40, 0xf0, 0x70, 0x07, 0x00, 0xd0, 0x70, 0x3f, 0xc0, 0xdc, 0xdc,
0x70, 0x70, 0x07, 0x00, 0xd0, 0x00, 0x1c, 0x00, 0x34, 0x0c, 0x03, 0x40, 0x0d, 0x00, 0x3c, 0x00,
0xf0, 0x00, 0x2b, 0xf0, 0xf2, 0xf0, 0xbf, 0xe0, 0x0f, 0x00, 0xbf, 0xe0, 0x2f, 0x80, 0xf8, 0xbc,
0xf0, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x3f, 0xc0, 0xff, 0xf8, 0x3f, 0xc0, 0x0f, 0x00, 0x2c, 0x00,
0xf0, 0x00, 0x03, 0xf0, 0xf0, 0xf0, 0x3f, 0xc0, 0x0f, 0x00, 0x3f, 0xc0, 0x0f, 0x00, 0xf0, 0x3c,
0xf0, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x3f, 0xc0, 0xff, 0xf0, 0x3f, 0xc0, 0x0f, 0x00, 0x0c, 0x00,
0xa0, 0x00, 0x02, 0xa0, 0xa0, 0xa0, 0x2a, 0x80, 0x0a, 0x00, 0x2a, 0x80, 0x0a, 0x00, 0xa0, 0x28,
0xa0, 0xa0, 0x0a, 0x00, 0xaa, 0xa0, 0x2a, 0x80, 0xaa, 0xa0, 0x2a, 0x80, 0x0a, 0x00, 0x08, 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
};
holeydma reversed scattered(16,2) char bb_char1[32] = {
0x01, 0x00, 0x01, 0x40, 0x0a, 0x94, 0x2a, 0x90, 0x3b, 0xa0, 0xc8, 0xe5, 0xc8, 0xe4, 0xc8, 0xd0,
0xc8, 0xe5, 0xbb, 0x84, 0x0c, 0x20, 0x2a, 0x90, 0x0e, 0x50, 0x3f, 0x94, 0x3d, 0x68, 0x5d, 0x6a
};
reversed scattered(16,2) char chest[32] = {
0x00, 0x00, 0x05, 0x50, 0x2b, 0xf4, 0x96, 0xfc, 0x69, 0xbd, 0xbe, 0x6a, 0x55, 0x55, 0xaa, 0xbf,
0xd5, 0xd5, 0xd9, 0xb7, 0xef, 0x9d, 0xef, 0x9d, 0xef, 0x9d, 0xdd, 0x9d, 0xd5, 0xf7, 0xff, 0x95
};

View File

@ -0,0 +1,16 @@
// example from https://github.com/steux/cc7800 - license: GPLv3
#include "conio.h"
char i;
void main()
{
clrscr();
for (i = 0; i != 8; i++) {
textcolor(i);
gotoxy(0, i);
cputs("Hello World!");
}
while(1);
}

View File

@ -0,0 +1,69 @@
// example from https://github.com/steux/cc7800 - license: GPLv3
#include "conio.h"
#include "assert.h"
char i;
reversed scattered(8,1) char special_char[8] = {
0x66, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00
};
void main()
{
clrscr();
// Draw a square
gotoxy(0, 0);
textcolor(7);
putch(CONIO_TL_CORNER);
for (i = 0; i != 20; i++) {
putch(CONIO_HBAR);
}
putch(CONIO_TR_CORNER);
for (i = 0; i != 8; i++) {
gotoxy(0, i + 1);
putch(CONIO_VBAR);
gotoxy(21, i + 1);
putch(CONIO_VBAR);
}
gotoxy(0, 9);
putch(CONIO_BL_CORNER);
for (i = 0; i != 20; i++) {
putch(CONIO_HBAR);
}
putch(CONIO_BR_CORNER);
// Write some text
for (i = 0; i != 8; i++) {
textcolor(i);
gotoxy(i + 1, i + 1);
cputs("Hello World!");
}
// Long text test
gotoxy(0, 10);
cputs("This is a long text that fits in a line.");
gotoxy(10, 11);
cputs("World!");
gotoxy(4, 11);
cputs("Hello");
gotoxy(10, 12);
cputs("World!");
gotoxy(4, 12);
textcolor(4);
cputs("Hello");
gotoxy(0, 13);
for (i = 0; i != 8; i++) {
textcolor(i);
putch('!');
}
gotoxy(0, 14);
for (i = 0; i != 8; i++) {
textcolor(7 - i);
putch(128); // Special character
}
while(1);
}

View File

@ -0,0 +1,7 @@
* = $0801
!word Start
!byte $00,$00,$9e
!text "2066"
!byte $00,$00,$00
* = $0812

View File

@ -0,0 +1,30 @@
* = $7ffe
; 2-byte load address for ROM image
!word $8000
; http://swut.net/c64cart-howto.html
; https://codebase64.org/doku.php?id=base:assembling_your_own_cart_rom_image
!word CartKReset ; cold start vector
!word CartWStart ; warm start vector
!byte $c3, $c2, $cd, $38, $30 ; "CBM80"
CartKReset
STX $D016 ; Turn on VIC for PAL / NTSC check
JSR $FDA3 ; IOINIT - Init CIA chips
JSR $FD50 ; RANTAM - Clear/test system RAM
JSR $FD15 ; RESTOR - Init KERNAL RAM vectors
JSR $FF5B ; CINT - Init VIC and screen editor
CLI ; Re-enable IRQ interrupts
CartBReset
; init BASIC?
!ifdef CART_INIT_BASIC {
JSR $E453 ; Init BASIC RAM vectors
JSR $E3BF ; Main BASIC RAM Init routine
JSR $E422 ; Power-up message / NEW command
LDX #$FB
TXS ; Reduce stack pointer for BASIC
; don't init BASIC, just NOP
} else {
!fill 12, $ea ; nop
}
CartWStart
; should be * = $x025

View File

@ -5,6 +5,10 @@ void raster_wait(byte line) {
while (VIC.rasterline < line) ;
}
void wait_vblank(void) {
raster_wait(250);
}
static byte VIC_BANK_PAGE[4] = {
0xc0, 0x80, 0x40, 0x00
};
@ -24,3 +28,13 @@ char __fastcall__ poll_keyboard() {
}
#endif
void set_raster_irq(char scanline) {
// deactivate CIA interrupts (keyboard, etc)
CIA1.icr = 0x7f;
// set raster line for interrupt
VIC.ctrl1 &= 0x7f; // clear raster line bit 8
VIC.rasterline = scanline;
// activate VIC raster interrupts
VIC.imr = 1;
}

View File

@ -24,14 +24,43 @@ typedef enum { false, true } bool; // boolean
///// MACROS /////
// VIC Control Register 1 Flags
#define VIC_CTRL1_RST8 0x80 // Bit 8 of RASTER (read) or raster line interrupt set (write)
#define VIC_CTRL1_ECM 0x40 // Extended Color Mode
#define VIC_CTRL1_BMM 0x20 // Bitmap Mode
#define VIC_CTRL1_DEN 0x10 // Display Enable
#define VIC_CTRL1_RSEL 0x08 // Row Select (25 or 24 rows)
#define VIC_CTRL1_YSCROLL_MASK 0x07 // Vertical Fine Scrolling
// VIC Control Register 2 Flags
#define VIC_CTRL2_RES 0x20 // Chip reset
#define VIC_CTRL2_MCM 0x10 // Multicolor Mode Enable
#define VIC_CTRL2_CSEL 0x08 // Column Select (40 or 38 columns)
#define VIC_CTRL2_XSCROLL_MASK 0x07 // Horizontal Fine Scrolling
// VIC Memory Control Register Flags
#define VIC_ADDR_VM_MASK 0xf0 // Video Matrix Base Address Mask (character data)
#define VIC_ADDR_CB_MASK 0x0e // Character Bank Base Address Mask (screen memory)
// VIC Interrupt Register Flags
#define VIC_IRR_IRQ 0x80 // Interrupt Request
#define VIC_IRR_ILP 0x08 // Light Pen Interrupt
#define VIC_IRR_IMMC 0x04 // Sprite-Sprite Collision Interrupt
#define VIC_IRR_IMBC 0x02 // Sprite-Background Collision Interrupt
#define VIC_IRR_IRST 0x01 // Raster Line Interrupt
// VIC Interrupt Mask Register Flags
#define VIC_IMR_ELP 0x08 // Enable Light Pen Interrupt
#define VIC_IMR_EMMC 0x04 // Enable Sprite-Sprite Collision Interrupt
#define VIC_IMR_EMBC 0x02 // Enable Sprite-Background Collision Interrupt
#define VIC_IMR_ERST 0x01 // Enable Raster Interrupt
// lookup screen address macro
#define SCRNADR(base,col,row) ((base)+(col)+(row)*40)
// default screen base address on startup
#define DEFAULT_SCREEN ((void*)0x400)
// wait until next frame, same as waitvsync()
#define wait_vblank waitvsync
// is raster line > 255?
#define RASTER_HIBIT (VIC.ctrl1 & 0x80)
@ -49,10 +78,10 @@ typedef enum { false, true } bool; // boolean
// set scrolling registers
#define SET_SCROLL_Y(_y) \
VIC.ctrl1 = (VIC.ctrl1 & 0xf8) | (_y);
VIC.ctrl1 = (VIC.ctrl1 & 0xf8) | (_y & 7);
#define SET_SCROLL_X(_x) \
VIC.ctrl2 = (VIC.ctrl2 & 0xf8) | (_x);
VIC.ctrl2 = (VIC.ctrl2 & 0xf8) | (_x & 7);
// enable RAM from 0xa000-0xffff, disable interrupts
@ -71,12 +100,25 @@ typedef enum { false, true } bool; // boolean
// wait until specific raster line
void raster_wait(byte line);
// wait until end of frame
void wait_vblank();
// get current VIC bank start address
char* get_vic_bank_start();
// get current screen memory address
char* get_screen_memory();
// read joystick fast
#define READ_STICK(index) ~PEEK(0xdc01-(index))
#define STICK_UP(joy) ((joy & 0x1) != 0)
#define STICK_DOWN(joy) ((joy & 0x2) != 0)
#define STICK_LEFT(joy) ((joy & 0x4) != 0)
#define STICK_RIGHT(joy) ((joy & 0x8) != 0)
#define STICK_BUTTON(joy) ((joy & 0x10) != 0)
#define STICK_MOVED(joy) ((joy & 0x1f) != 0)
#ifdef __CC65__
// return key in buffer, or 0 if none (BIOS call)
char __fastcall__ poll_keyboard();
@ -91,4 +133,9 @@ inline void waitvsync() {
}
#endif
// for use with set_irq()
// sets up the VIC to send raster interrupts
// and disables CIA interrupts
void set_raster_irq(char scanline);
#endif

42
presets/c64/hello.acme Normal file
View File

@ -0,0 +1,42 @@
!src "cartheader.acme"
!address {
Temp = $02
}
Start:
sei ; turn off interrupts
ldy #0
Loop:
lda Message,y ; load message byte
beq EOM ; 0 = end of string
clc
adc #$40
sta $400+41,y ; store to screen
iny
bne Loop ; next character
EOM:
Wait1:
lda $d011
bmi Wait1 ; wait for line < 256
Wait2:
lda $d012 ; get current scanline
Wait3:
cmp $d012
beq Wait3 ; wait for scanline to change
lsr ; divide by 2
lsr ; divide by 2
clc
adc Temp ; add to frame counter
sta $d020 ; set border color
lda $d011 ; get status bits
bpl Wait2 ; repeat until line >= 256
sty $d020 ; reset border color
dec Temp ; change frame counter
jmp Wait1 ; endless loop
Message:
!scr "HELLO WORLD", 0

16
presets/c64/helloc.c Normal file
View File

@ -0,0 +1,16 @@
#include <stdio.h>
#include <conio.h>
#include <c64.h>
#include <cbm_petscii_charmap.h>
void main(void) {
clrscr(); // clear screen
puts("Hello World!\n"); // write message at cursor
chline(12); // horizontal line
bordercolor(COLOR_LIGHTBLUE); // set color to blue
bgcolor(COLOR_GREEN); // set background color
textcolor(COLOR_YELLOW); // set text color
puts("\nThis text is yellow!\n"); // write message
cgetc(); // wait for input
}

View File

@ -1,9 +1,12 @@
VIC_BASE = $0
VIC_SCRN_BASE = VIC_BASE + $400
MAX_MSPRITES = 28
MIN_Y_SPACING = 35
DEBUG = 1
DEBUG = 0
.code
@ -31,7 +34,7 @@ _msprite_render_section:
adc #MIN_Y_SPACING
sta bailout_line
@loop:
.ifdef DEBUG
.if DEBUG
inc $d020
.endif
lda $d012
@ -66,7 +69,7 @@ _msprite_render_section:
sta $d027,x
; POKE(0x7f8+j, msprite_shape[i]);
lda _msprite_shape,y
sta $07f8,x
sta VIC_SCRN_BASE + $03f8,x
; set hi X bit
lda _msprite_x_hi,y
lsr
@ -83,7 +86,7 @@ _msprite_render_section:
sta j ; next h/w sprite
jmp @loop
@loopexit:
.ifdef DEBUG
.if DEBUG
lda #0
sta $d020
.endif

View File

@ -9,6 +9,7 @@ A simple music player.
#include "sidmacros.h"
#include <cbm_petscii_charmap.h>
#include <6502.h>
// SID frequency table (PAL version)
const int note_table_pal[96] = {
@ -25,10 +26,9 @@ void sid_init() {
byte music_index = 0;
byte cur_duration = 0;
byte music_wavebits = 0;
byte music_wavebits = SID_SQUARE;
const byte music1[]; // music data -- see end of file
const byte* music_ptr = music1;
const byte* music_ptr = 0;
byte next_music_byte() {
return *music_ptr++;
@ -149,7 +149,7 @@ void drawParams() {
}
}
void setParamValues() {
void setSIDRegisters() {
char i;
word val;
char buf[30];
@ -179,57 +179,63 @@ void setParamValues() {
music_wavebits = buf[0x04];
}
void tick(int i) {
while (i--) {
wait_vblank();
play_music();
}
const byte music1[]; // music data -- see end of file
char music_update() {
if (!music_ptr) start_music(music1);
play_music();
return IRQ_NOT_HANDLED;
}
void handleInput() {
char key = 0;
char joy = joy_read(0);
if (joy == 0) return;
if (JOY_UP(joy)) key = 'i';
if (JOY_DOWN(joy)) key = 'k';
if (JOY_LEFT(joy)) key = 'j';
if (JOY_RIGHT(joy)) key = 'l';
switch (key) {
case 'i': // UP
if (currentParam > 0) {
--currentParam;
drawValue(currentParam+1);
drawValue(currentParam);
tick(3);
}
break;
case 'k': // DOWN
if (currentParam < NPARAMS - 1) {
++currentParam;
drawValue(currentParam-1);
drawValue(currentParam);
tick(3);
}
break;
case 'j': // LEFT
if (paramValues[currentParam] > SID_PARAMS[currentParam].low) {
paramValues[currentParam]--;
drawValue(currentParam);
setParamValues();
}
break;
case 'l': // RIGHT
if (paramValues[currentParam] < SID_PARAMS[currentParam].high) {
paramValues[currentParam]++;
drawValue(currentParam);
setParamValues();
}
break;
}
switch (key) {
case 'i': // UP
if (currentParam > 0) {
--currentParam;
drawValue(currentParam+1);
drawValue(currentParam);
}
break;
case 'k': // DOWN
if (currentParam < NPARAMS - 1) {
++currentParam;
drawValue(currentParam-1);
drawValue(currentParam);
}
break;
case 'j': // LEFT
if (paramValues[currentParam] > SID_PARAMS[currentParam].low) {
paramValues[currentParam]--;
drawValue(currentParam);
setSIDRegisters();
}
break;
case 'l': // RIGHT
if (paramValues[currentParam] < SID_PARAMS[currentParam].high) {
paramValues[currentParam]++;
drawValue(currentParam);
setSIDRegisters();
}
break;
}
// delay a few frames to slow down movement
waitvsync();
waitvsync();
waitvsync();
}
void main(void)
{
joy_install (joy_static_stddrv);
// set initial SID parameters
paramValues[0] = 15;
paramValues[1] = 8;
paramValues[2] = 8;
@ -237,14 +243,19 @@ void main(void)
paramValues[4] = 4;
paramValues[5] = 4;
paramValues[7] = 1; // pulse
setSIDRegisters();
// draw the UI
drawParams();
setParamValues();
// set IRQ routine called every frame
set_irq(music_update, (void*)0x9f00, 0x100);
// main loop to handle UI
music_ptr = 0;
while (1) {
waitvsync();
handleInput();
if (!music_ptr) start_music(music1);
tick(1);
}
}

300
presets/c64/plasma.c Normal file
View File

@ -0,0 +1,300 @@
/*****************************************************************************\
** plasma test program for cc65. **
** **
** (w)2001 by groepaz **
** **
** Cleanup and porting by Ullrich von Bassewitz. **
** **
\*****************************************************************************/
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <cc65.h>
#if defined(__C64__) || defined(__C128__)
# define SCREEN1 0xE000
# define SCREEN2 0xE400
# define CHARSET 0xE800
# define outb(addr,val) (*(addr)) = (val)
# define inb(addr) (*(addr))
#elif defined(__CBM510__)
# define SCREEN1 0xF000
# define SCREEN2 0xF400
# define CHARSET 0xE000
# define outb(addr,val) pokebsys ((unsigned)(addr), val)
# define inb(addr) peekbsys ((unsigned)(addr))
#elif defined(__PLUS4__)
# define SCREEN1 0x6400
# define SCREEN2 0x6C00
# define CHARSET 0x7000
# define outb(addr,val) (*(addr)) = (val)
# define inb(addr) (*(addr))
#endif
/* Values for the VIC address register to switch between the two pages */
#if defined(__PLUS4__)
#define PAGE1 ((SCREEN1 >> 8) & 0xF8)
#define PAGE2 ((SCREEN2 >> 8) & 0xF8)
#define CHARADR ((CHARSET >> 8) & 0xFC)
#else
#define PAGE1 ((SCREEN1 >> 6) & 0xF0) | ((CHARSET >> 10) & 0x0E)
#define PAGE2 ((SCREEN2 >> 6) & 0xF0) | ((CHARSET >> 10) & 0x0E)
#endif
/* Use static local variables for speed */
#pragma static-locals (1);
static const unsigned char sinustable[0x100] = {
0x80, 0x7d, 0x7a, 0x77, 0x74, 0x70, 0x6d, 0x6a,
0x67, 0x64, 0x61, 0x5e, 0x5b, 0x58, 0x55, 0x52,
0x4f, 0x4d, 0x4a, 0x47, 0x44, 0x41, 0x3f, 0x3c,
0x39, 0x37, 0x34, 0x32, 0x2f, 0x2d, 0x2b, 0x28,
0x26, 0x24, 0x22, 0x20, 0x1e, 0x1c, 0x1a, 0x18,
0x16, 0x15, 0x13, 0x11, 0x10, 0x0f, 0x0d, 0x0c,
0x0b, 0x0a, 0x08, 0x07, 0x06, 0x06, 0x05, 0x04,
0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03,
0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x0a,
0x0b, 0x0c, 0x0d, 0x0f, 0x10, 0x11, 0x13, 0x15,
0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24,
0x26, 0x28, 0x2b, 0x2d, 0x2f, 0x32, 0x34, 0x37,
0x39, 0x3c, 0x3f, 0x41, 0x44, 0x47, 0x4a, 0x4d,
0x4f, 0x52, 0x55, 0x58, 0x5b, 0x5e, 0x61, 0x64,
0x67, 0x6a, 0x6d, 0x70, 0x74, 0x77, 0x7a, 0x7d,
0x80, 0x83, 0x86, 0x89, 0x8c, 0x90, 0x93, 0x96,
0x99, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae,
0xb1, 0xb3, 0xb6, 0xb9, 0xbc, 0xbf, 0xc1, 0xc4,
0xc7, 0xc9, 0xcc, 0xce, 0xd1, 0xd3, 0xd5, 0xd8,
0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8,
0xea, 0xeb, 0xed, 0xef, 0xf0, 0xf1, 0xf3, 0xf4,
0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xfb, 0xfc,
0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd,
0xfd, 0xfc, 0xfb, 0xfa, 0xfa, 0xf9, 0xf8, 0xf6,
0xf5, 0xf4, 0xf3, 0xf1, 0xf0, 0xef, 0xed, 0xeb,
0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, 0xdc,
0xda, 0xd8, 0xd5, 0xd3, 0xd1, 0xce, 0xcc, 0xc9,
0xc7, 0xc4, 0xc1, 0xbf, 0xbc, 0xb9, 0xb6, 0xb3,
0xb1, 0xae, 0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c,
0x99, 0x96, 0x93, 0x90, 0x8c, 0x89, 0x86, 0x83
};
static void doplasma (register unsigned char* scrn)
{
unsigned char xbuf[40];
unsigned char ybuf[25];
unsigned char c1a,c1b;
unsigned char c2a,c2b;
unsigned char c1A,c1B;
unsigned char c2A,c2B;
register unsigned char i, ii;
c1a = c1A;
c1b = c1B;
for (ii = 0; ii < 25; ++ii) {
ybuf[ii] = (sinustable[c1a] + sinustable[c1b]);
c1a += 4;
c1b += 9;
}
c1A += 3;
c1B -= 5;
c2a = c2A;
c2b = c2B;
for (i = 0; i < 40; ++i) {
xbuf[i] = (sinustable[c2a] + sinustable[c2b]);
c2a += 3;
c2b += 7;
}
c2A += 2;
c2B -= 3;
for (ii = 0; ii < 25; ++ii) {
/* Unrolling the following loop will give a speed increase of
** nearly 100% (~24fps), but it will also increase the code
** size a lot.
*/
for (i = 0; i < 40; ++i, ++scrn) {
*scrn = (xbuf[i] + ybuf[ii]);
}
}
}
static void makechar (void)
{
static const unsigned char bittab[8] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};
unsigned char i, ii, b, s;
unsigned c;
gotoxy (0, 1);
for (c = 0; c < 0x100; ++c) {
s = sinustable[c];
for (i = 0; i < 8; ++i){
b = 0;
for (ii = 0; ii < 8; ++ii) {
if ((rand() & 0xFFu) > s) {
b |= bittab[ii];
}
}
((unsigned char*)CHARSET) [(c*8) + i] = b;
}
if ((c & 0x07) == 0) {
cputc ('.');
}
}
}
int main (void)
{
unsigned char border;
unsigned char background;
unsigned char text;
unsigned char v;
clock_t t;
unsigned long f = 0;
unsigned long sec;
unsigned sec10;
unsigned long fps;
unsigned fps10;
#if defined(__C64__)
unsigned char block;
#endif
#if defined(__C128__)
unsigned char block;
unsigned char initflag;
unsigned char graphflag;
#endif
#if defined(__PLUS4__)
unsigned int i;
unsigned char v2;
#endif
clrscr ();
cprintf ("Making charset, mompls");
makechar();
/* Set the border and background colors */
border = bordercolor (COLOR_BLUE);
background = bgcolor (COLOR_BLUE);
text = textcolor (COLOR_BLACK);
clrscr ();
#if defined(__C64__) || defined(__C128__)
/* Move the VIC 16K block */
block = inb (&CIA2.pra);
outb (&CIA2.pra, (block & 0xFC) | ((SCREEN1 >> 14) ^ 0x03));
#endif
#if defined(__C128__)
/* Save and change some flags, so that kernal/basic interrupt handler will
** not interfere with our routine.
*/
initflag = *(unsigned char*) 0xA04;
*(unsigned char*) 0xA04 &= 0xFE;
graphflag = *(unsigned char*) 0xD8;
*(unsigned char*) 0xD8 = 0xFF;
#endif
/* Remember the VIC address register */
#if defined(__PLUS4__)
v = inb (&TED.char_addr);
v2 = inb (&TED.video_addr);
#else
v = inb (&VIC.addr);
#endif
#if defined(__PLUS4__)
for (i=0;i<1000;i++) {
((unsigned char *) (SCREEN1-0x0400))[i] = 0;
((unsigned char *) (SCREEN2-0x0400))[i] = 0;
}
outb (&TED.char_addr, CHARADR);
#endif
/* Run the demo until a key was hit */
t = clock ();
while (!kbhit()) {
/* Build page 1, then make it visible */
doplasma ((unsigned char*)SCREEN1);
#if defined(__PLUS4__)
outb (&TED.video_addr, PAGE1);
#else
outb (&VIC.addr, PAGE1);
#endif
/* Build page 2, then make it visible */
doplasma ((unsigned char*)SCREEN2);
#if defined(__PLUS4__)
outb (&TED.video_addr, PAGE2);
#else
outb (&VIC.addr, PAGE2);
#endif
/* Count frames */
f += 2;
}
t = clock() - t;
/* Switch back the VIC screen */
#if defined(__PLUS4__)
outb (&TED.video_addr, v2);
outb (&TED.char_addr, v);
#else
outb (&VIC.addr, v);
#endif
#if defined(__C64__) || defined(__C128__)
/* Move back the VIC 16K block */
outb (&CIA2.pra, block);
#endif
#if defined(__C128__)
/* Restore the flags */
*(unsigned char*) 0xA04 = initflag;
*(unsigned char*) 0xD8 = graphflag;
#endif
/* Fetch the character from the keyboard buffer and discard it */
(void) cgetc();
/* Reset screen colors */
bordercolor (border);
bgcolor (background);
textcolor (text);
clrscr ();
/* Calculate stats */
sec = (t * 10) / CLK_TCK;
sec10 = sec % 10;
sec /= 10;
fps = (f * (CLK_TCK * 10)) / t;
fps10 = fps % 10;
fps /= 10;
/* Output stats */
gotoxy (0, 0); cprintf ("time : %lu.%us", sec, sec10);
gotoxy (0, 1); cprintf ("frames: %lu", f);
gotoxy (0, 2); cprintf ("fps : %lu.%u", fps, fps10);
if (doesclrscrafterexit ()) {
cputsxy (0, 4, "Press any key when done...");
(void) cgetc ();
}
/* Done */
return EXIT_SUCCESS;
}

View File

@ -1,4 +1,5 @@
; use CC65's interrupter (slower)
USE_INTERRUPTOR = 0
.segment "DATA"
@ -9,39 +10,41 @@ NextDlist: .word NullDlist-1
.segment "CODE"
.global ___dlist_setup
.global ___dlist_done
.global DLIST_IRQ_NEXT
.global DLIST_IRQ_RESTART
.if USE_INTERRUPTOR
.interruptor DLIST_IRQ
.endif
___dlist_setup:
SEI ; set interrupt bit, make the CPU ignore interrupt requests
sei ; set interrupt bit, make the CPU ignore interrupt requests
sta StartDlist+0 ; save XA as pointer to start of dlist
stx StartDlist+1
LDA #%01111111 ; switch off interrupt signals from CIA-1
STA $DC0D
lda #%01111111 ; switch off interrupt signals from CIA-1
sta $DC0D
AND $D011 ; clear most significant bit of VIC's raster register
STA $D011
and $D011 ; clear most significant bit of VIC's raster register
sta $D011
LDA $DC0D ; acknowledge pending interrupts from CIA-1
LDA $DD0D ; acknowledge pending interrupts from CIA-2
lda $DC0D ; acknowledge pending interrupts from CIA-1
lda $DD0D ; acknowledge pending interrupts from CIA-2
LDA #252 ; set rasterline where interrupt shall occur
STA $D012
lda #252 ; set rasterline where interrupt shall occur
sta $D012
.if !USE_INTERRUPTOR
LDA #<DLIST_IRQ ; set interrupt vectors, pointing to interrupt service routine below
STA $0314
LDA #>DLIST_IRQ
STA $0315
lda #<DLIST_IRQ ; set interrupt vectors, pointing to interrupt service routine below
sta $0314
lda #>DLIST_IRQ
sta $0315
.endif
LDA #%00000001 ; enable raster interrupt signals from VIC
STA $D01A
lda #%00000001 ; enable raster interrupt signals from VIC
sta $D01A
cli
rts
@ -54,26 +57,21 @@ DLIST_CALL:
rts
DLIST_IRQ_RESTART:
sta $d012
sta $D012 ; set IRQ raster line
lda StartDlist+0
sta NextDlist+0
lda StartDlist+1
sta NextDlist+1
bne DLIST_ACK
DLIST_IRQ_STOP:
lda #0 ; disable raster interrupt signals from VIC
sta $D01A
bne DLIST_ACK
DLIST_IRQ_NEXT:
sta $d012
sta $D012
pla
sta NextDlist+0
pla
sta NextDlist+1
DLIST_ACK:
ASL $D019 ; acknowledge the interrupt by clearing the VIC's interrupt flag
asl $D019 ; acknowledge the interrupt by clearing the VIC's interrupt flag
.if USE_INTERRUPTOR
clc
rts
@ -84,9 +82,30 @@ DLIST_ACK:
tax
pla
rti ; return from interrupt
; JMP $EA31 ; jump into KERNAL's standard interrupt service routine to handle keyboard scan, cursor display etc.
.endif
___dlist_done:
php
sei ; disable interrupts
lda #$0 ; disable raster interrupt signals from VIC
sta $D01A
lda #$ff
sta $DC0D
.if !USE_INTERRUPTOR
lda #$31 ; set interrupt vectors back to KERNAL
sta $0314
lda #$ea
sta $0315
.else
lda #<(NullDlist-1)
sta StartDlist
lda #>(NullDlist-1)
sta StartDlist+1
.endif
plp
rts
NullDlist:
lda #252
jmp DLIST_IRQ_RESTART

View File

@ -4,6 +4,7 @@
// internal function, use macro instead
void __dlist_setup(void* ptr);
void __dlist_done();
// initialize display list with function 'func'
#define DLIST_SETUP(func) \
@ -19,5 +20,7 @@ void __dlist_setup(void* ptr);
__A__ = line; \
asm ("jmp DLIST_IRQ_RESTART");
// stop display list
#define DLIST_DONE() __dlist_done();
#endif

29
presets/c64/screen_ram.c Normal file
View File

@ -0,0 +1,29 @@
#include "common.h"
//#link "common.c"
#include <cbm_screen_charmap.h>
void main(void) {
unsigned int i;
clrscr(); // clear the screen
POKE(0x400, 'A'); // write to first byte of screen memory
POKE(0x400, 65); // character code for 'A'
POKE(0x400 + 40*24 + 39, 'Z'); // row 24, column 39
// fill with random characters
for (i=0; i<40*25; i++)
POKE(0x400 + i, 205 + (rand() & 1));
// set character set to uppercase + graphics characters
SET_VIC_BITMAP(0x1000);
// set color map underlying characters
for (i=0; i<40*25; i++)
COLOR_RAM[i] = COLOR_GREEN;
// infinite loop (avoid "ready" prompt)
while (1);
}

View File

@ -63,8 +63,8 @@ void main(void) {
// infinite loop
while (1) {
// wait for vsync
waitvsync();
// wait for end of frame
wait_vblank();
// scroll one pixel to the left
// and move screen memory every 8 pixels
scroll_one_pixel_left();

View File

@ -23,8 +23,8 @@ void scroll_one_pixel_left() {
src = scrnbuf[visbuf] + (scroll_x & 7) * 128;
// destination = hidden buffer
dst = scrnbuf[visbuf ^ 1] + (scroll_x & 7) * 128;
// wait for vsync
waitvsync();
// wait for end of frame
wait_vblank();
// scroll hidden buffer
memcpy(dst, src+1, 128);
// every 8 pixels, switch visible and hidden buffers

View File

@ -17,8 +17,8 @@ void scroll_update_regs() {
void scroll_swap() {
// swap hidden and visible buffers
hidbuf ^= 1;
// wait for vblank and update registers
waitvsync();
// wait for end of frame and update registers
wait_vblank();
scroll_update_regs();
SET_VIC_SCREEN(hidbuf ? 0x8000 : 0x8400);
}

View File

@ -83,8 +83,8 @@ void main(void) {
// animate sprite in shadow sprite ram
sprite_draw(0, n++, 70, 192);
sprite_draw(0, 172, 145, 192);
// wait for vblank
waitvsync();
// wait for end of frame
wait_vblank();
// update scroll registers
// and swap screens if we must
scroll_update();

View File

@ -118,7 +118,7 @@ void main(void) {
// animate sprite in shadow sprite ram
update_player();
// wait for end of frame
waitvsync();
wait_vblank();
// then update sprite registers
sprite_update(visbuf);
// update scroll registers

179
presets/c64/scrollingmap1.c Normal file
View File

@ -0,0 +1,179 @@
#include <stdio.h>
#include <conio.h>
#include <c64.h>
#include <cbm_petscii_charmap.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <joystick.h>
//#resource "c64-sid.cfg"
#define CFGFILE c64-sid.cfg
#include "common.h"
//#link "common.c"
#include "scrolling.h"
//#link "scrolling.c"
#include "sprites.h"
//#link "sprites.c"
//#link "level1.ca65"
extern const byte charset_data[];
extern const byte charset_attrib_data[];
extern const byte chartileset_data[];
extern const byte chartileset_tag_data[];
extern const byte* map_row_pointers[];
#define MAP_COLS 28
#define MAP_ROWS 11
static void draw_cell(word ofs, byte x, byte y) {
sbyte xx = x + origin_x;
sbyte yy = y + origin_y;
sbyte col = xx >> 2;
sbyte row = yy >> 2;
byte xofs = xx & 3;
byte yofs = yy & 3;
char ch;
char color;
if (col < 0 || col >= MAP_COLS || row < 0 || row >= MAP_ROWS) {
ch = 0;
color = 0;
} else {
byte tileindex = map_row_pointers[row][col];
ch = chartileset_data[xofs + yofs*4 + tileindex*16];
color = charset_attrib_data[ch];
}
hidbuf[ofs] = ch;
colorbuf[ofs] = color;
}
void scroll_draw_column(byte col) {
byte y;
word ofs = col;
for (y=0; y<ROWS; y++) {
draw_cell(ofs, col, y);
ofs += COLS;
}
}
void scroll_draw_row(byte row) {
byte x;
word ofs = row * COLS;
for (x=0; x<COLS; x++) {
draw_cell(ofs, x, row);
++ofs;
}
}
/*{w:24,h:21,bpp:1,brev:1}*/
const char SPRITE1[3*21] = {
0x00,0x7F,0x00,0x01,0xFF,0xC0,0x03,0xFF,0xE0,
0x03,0xE7,0xE0,0x07,0xD9,0xF0,0x07,0xDF,0xF0,
0x07,0xD9,0xF0,0x03,0xE7,0xE0,0x03,0xFF,0xE0,
0x03,0xFF,0xE0,0x02,0xFF,0xA0,0x01,0x7F,0x40,
0x01,0x3E,0x40,0x00,0x9C,0x80,0x00,0x9C,0x80,
0x00,0x49,0x00,0x00,0x49,0x00,0x00,0x3E,0x00,
0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x1C,0x00
};
int playerx = 0;
int playery = 0;
int camerax = 0;
int cameray = 0;
void update_player() {
sprite_draw(0, playerx-camerax+172, playery-cameray+140, 255);
}
void camera_follow(byte moving) {
int dx, dy;
dx = camerax - playerx;
dy = cameray - playery;
if (moving && abs(dx) < 32 && abs(dy) < 32) return;
dx >>= 4;
dy >>= 4;
if (dx) {
if (dx > 8) dx = 8;
else if (dx < -8) dx = -8;
camerax -= dx;
scroll_horiz(dx);
}
if (dy) {
if (dy > 8) dy = 8;
else if (dy < -8) dy = -8;
cameray -= dy;
scroll_vert(dy);
}
}
void refresh_world(void) {
byte i;
for (i=0; i<25; i++) {
scroll_draw_row(i);
}
}
void main(void) {
clrscr();
// setup scrolling library
scroll_setup();
// multicolor character mode
VIC.ctrl2 |= 0x10;
VIC.bgcolor0 = 6;
VIC.bgcolor1 = 0;
VIC.bgcolor2 = 1;
// select character set @ 0x8800
VIC.addr = 0x12;
memcpy((char*)0x8800, charset_data, 520);
// setup sprite library and copy sprite to VIC bank
sprite_clear();
sprite_set_shapes(SPRITE1, 255, 1);
sprshad.spr_color[0] = 13;
// install the joystick driver
joy_install (joy_static_stddrv);
// repaint screen memory w/ the map
refresh_world();
// infinite loop
while (1) {
static char speed;
static char joy;
static bool slowframe = false;
// get joystick bits
joy = joy_read(0);
// speed up scrolling while button pressed
speed = JOY_BTN_1(joy) ? 3 : 1;
// if we copied screen memory last frame,
// double speed of player for this frame
if (slowframe) speed *= 2;
// move sprite based on arrow keys
if (JOY_LEFT(joy)) playerx -= speed;
if (JOY_RIGHT(joy)) playerx += speed;
if (JOY_UP(joy)) playery -= speed;
if (JOY_DOWN(joy)) playery += speed;
// move the camera?
camera_follow(joy);
slowframe = swap_needed;
// animate sprite in shadow sprite ram
update_player();
// wait for vblank
wait_vblank();
// then update sprite registers
sprite_update(visbuf);
// update scroll registers
// and swap screens if we must
scroll_update();
}
}

View File

@ -3,6 +3,7 @@
//#link "common.c"
#include <tgi.h>
#include <6502.h>
//#resource "c64-sid.cfg"
#define CFGFILE c64-sid.cfg
@ -57,6 +58,11 @@ void show_envelope() {
if (++sweep == 320) sweep = 0;
}
char music_update() {
sid_update();
return IRQ_NOT_HANDLED;
}
void main(void) {
// install TGI graphics driver
tgi_install(tgi_static_stddrv);
@ -71,6 +77,9 @@ void main(void) {
// install joystick driver
joy_install(joy_static_stddrv);
// set IRQ routine called every frame
set_irq(music_update, (void*)0x9f00, 0x100);
while (1) {
// play sound effect when joystick is moved
byte joy = joy_read(0);
@ -81,8 +90,6 @@ void main(void) {
}
// sync with frame rate
waitvsync();
// update SID player
sid_update();
// update graphs
show_envelope();
show_signal();

View File

@ -124,6 +124,7 @@ void update_scoreboard() {
void add_score(int delta) {
score = bcd_add(score, delta);
update_scoreboard();
}
// clear scoreboard and draw initial strings
@ -242,13 +243,13 @@ void scroll_one_pixel_left() {
}
}
void detect_player_collision(byte bgcoll, byte sprcoll) {
void detect_player_collision(byte bg_coll, byte spr_coll) {
// did we hit a powerup? (#0 and #1)
bool hit_powerup = (sprcoll & 0b011) == 0b011;
bool hit_powerup = (spr_coll & 0b011) == 0b011;
// did player and obstacle sprite (#0 and #2) collide?
bool hit_obstacle = (sprcoll & 0b101) == 0b101;
bool hit_obstacle = (spr_coll & 0b101) == 0b101;
// did player (#0) collide with background?
hit_obstacle |= (bgcoll & 0b001) != 0;
hit_obstacle |= (bg_coll & 0b001) != 0;
// did we hit anything bad?
if (hit_obstacle) {
// make player fall downward and backward
@ -257,7 +258,6 @@ void detect_player_collision(byte bgcoll, byte sprcoll) {
sprshad.spr_color[PLAYER_INDEX] = COLOR_LIGHTRED;
SID_PLAY_TONE(500);
if (score != 0) { add_score(0x9999); } // BCD -1
update_scoreboard();
} else {
sprshad.spr_color[PLAYER_INDEX] = COLOR_GREEN;
}
@ -266,7 +266,6 @@ void detect_player_collision(byte bgcoll, byte sprcoll) {
sprshad.spr_color[POWERUP_INDEX] += 1; // cycle colors
SID_PLAY_TONE(8000);
add_score(1);
update_scoreboard();
}
}
@ -312,15 +311,15 @@ void main() {
// game loop, repeat forever
while (1) {
// saved collision flags
byte sprcoll, bgcoll;
byte spr_coll, bg_coll;
// wait for end of frame
waitvsync();
//--- START TIME CRITICAL SECTION
// grab and reset collision flags
sprcoll = VIC.spr_coll;
bgcoll = VIC.spr_bg_coll;
spr_coll = VIC.spr_coll;
bg_coll = VIC.spr_bg_coll;
// update sprite registers from sprite shadow buffer
sprite_update(DEFAULT_SCREEN);
@ -330,7 +329,7 @@ void main() {
//--- END TIME CRITICAL SECTION
// use collision flags to see if player collided
detect_player_collision(bgcoll, sprcoll);
detect_player_collision(bg_coll, spr_coll);
// get joystick bits and move player
move_player(joy_read(0));

18
presets/c64/skeleton.acme Normal file
View File

@ -0,0 +1,18 @@
!src "basicheader.acme"
Start:
jsr $e544 ; clear screen
ldy #0
Loop:
lda Message,y ; load message byte
beq EOM ; 0 = end of string
sta $400+41,y ; store to screen
iny
bne Loop ; next character
EOM:
jmp EOM ; infinite loop
Message:
!scr "hello world!", 0

View File

@ -0,0 +1,76 @@
//#link "common.c"
#include "common.h"
//#link "rasterirq.ca65"
#include "rasterirq.h"
//#link "sprites.c"
#include "sprites.h"
#include <cbm_petscii_charmap.h>
#include <cc65.h>
/*{w:24,h:21,bpp:1,brev:1}*/
const char spriteshape[3*21] = {
0x00,0x7F,0x00,0x01,0xFF,0xC0,0x03,0xFF,0xE0,
0x03,0xE7,0xE0,0x07,0xD9,0xF0,0x07,0xDF,0xF0,
0x07,0xD9,0xF0,0x03,0xE7,0xE0,0x03,0xFF,0xE0,
0x03,0xFF,0xE0,0x02,0xFF,0xA0,0x01,0x7F,0x40,
0x01,0x3E,0x40,0x00,0x9C,0x80,0x00,0x9C,0x80,
0x00,0x49,0x00,0x00,0x49,0x00,0x00,0x3E,0x00,
0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x1C,0x00
};
void sprite_stretch() {
// get current raster line
asm("lda $d012");
// sprite Y expand bits = 255
asm("ldx #$ff");
asm("stx $d017");
// wait for next raster line
asm("@loop:");
asm("cmp $d012");
asm("beq @loop");
// sprite Y expand bits = 0
asm("inx");
asm("stx $d017");
}
void dlist_example(void) {
// stretch for the next 40 lines
while (VIC.rasterline != 160) {
sprite_stretch();
}
VIC.spr0_y+=3;
VIC.spr7_y-=2;
DLIST_RESTART(8*15);
}
void main(void) {
byte i;
clrscr();
VIC.bordercolor = 0;
sprite_clear();
sprite_set_shapes(spriteshape, 192, 1);
sprshad.spr_exp_x = 0xff;
for (i=0; i<8; i++) {
sprshad.spr_color[i] = i|8;
sprite_draw(i, i*38+24, 120-i, 192);
}
sprite_update(DEFAULT_SCREEN);
DLIST_SETUP(dlist_example);
while (1) {
if (STICK_MOVED(READ_STICK(0))) break;
}
DLIST_DONE();
}

View File

@ -0,0 +1,92 @@
//#link "common.c"
#include "common.h"
//#link "rasterirq.ca65"
#include "rasterirq.h"
//#link "sprites.c"
#include "sprites.h"
#include <cbm_petscii_charmap.h>
#include <cc65.h>
/*{w:24,h:21,bpp:1,brev:1}*/
const char spriteshape[3*21] = {
0x00,0x7F,0x00,0x01,0xFF,0xC0,0x03,0xFF,0xE0,
0x03,0xE7,0xE0,0x07,0xD9,0xF0,0x07,0xDF,0xF0,
0x07,0xD9,0xF0,0x03,0xE7,0xE0,0x03,0xFF,0xE0,
0x03,0xFF,0xE0,0x02,0xFF,0xA0,0x01,0x7F,0x40,
0x01,0x3E,0x40,0x00,0x9C,0x80,0x00,0x9C,0x80,
0x00,0x49,0x00,0x00,0x49,0x00,0x00,0x3E,0x00,
0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x1C,0x00
};
byte scroll_x = 0;
byte scroll_y = 0;
void dlist_example(void) {
VIC.ctrl1 = VIC_CTRL1_DEN | VIC_CTRL1_RSEL;
VIC.bordercolor = 5;
// Flexible line distance (FLD)
// this adds a gap of 1-6 scanlines
DLIST_NEXT(150);
VIC.ctrl1 = (scroll_y & 7) | 0x18;
VIC.bordercolor = 2;
// this opens up the vertical borders
// it must be done on the last row (247-249)
DLIST_NEXT(249);
VIC.ctrl1 = VIC_CTRL1_DEN;
// move sprites and restart the display list
scroll_x++;
scroll_y++;
VIC.spr0_y++;
VIC.spr7_y--;
VIC.bordercolor = 4;
DLIST_RESTART(30);
}
void SieveOfEratosthenes() {
const int n = 1023;
int primes[1024];
int i,p;
memset(primes, 1, sizeof(primes));
for (p = 2; p*p <= n; p++) {
if (primes[p]) {
for (i = p*p; i <= n; i += p)
primes[i] = 0;
}
}
for (p = 2; p <= n; p++)
if (primes[p])
printf("%d ", p);
}
void main(void) {
byte i;
clrscr();
sprite_clear();
sprite_set_shapes(spriteshape, 192, 1);
sprshad.spr_exp_x = 0xff;
for (i=0; i<8; i++) {
sprshad.spr_color[i] = i+3;
sprite_draw(i, i*38+24, 248, 192);
}
sprite_update(DEFAULT_SCREEN);
DLIST_SETUP(dlist_example);
// do something complicated while IRQ runs...
while (1) {
SieveOfEratosthenes();
}
}

View File

@ -0,0 +1,143 @@
#include "common.h"
//#link "common.c"
#include "rasterirq.h"
//#link "rasterirq.ca65"
#include "bcd.h"
//#link "bcd.c"
///// DEFINES
#define GAME_BASE 0x400 // scrolling screen ram
#define SCORE_BASE 0x2c00 // scoreboard screen ram
#define SCROLL_TOP 8 // scroll top row
#define SCROLL_ROWS 14 // scroll # of rows
#define GROUND_ROW 7 // ground row (+ top row)
///// VARIABLES
word scroll_x = 0; // current scroll X position
word score = 0; // current player score
///// FUNCTIONS
// display list used by rasterirq.h
// draws scoreboard and sets scroll register
void display_list() {
// set x scroll register to scroll value
SET_SCROLL_X(scroll_x);
// set background color
VIC.bgcolor[0] = COLOR_CYAN;
// next interrupt is two rows from bottom
DLIST_NEXT(250-16);
// set background color
VIC.bgcolor[0] = COLOR_BLUE;
// screen memory = 0x2800
SET_VIC_SCREEN(SCORE_BASE);
// clear x scroll register
SET_SCROLL_X(0);
// next interrupt is bottom of frame
DLIST_NEXT(250);
// reset screen to 0x400
SET_VIC_SCREEN(0x400);
// next interrupt is above top of next frame
DLIST_RESTART(40);
}
void update_scoreboard() {
draw_bcd_word(SCRNADR(SCORE_BASE,7,24), score);
}
void add_score(int delta) {
score = bcd_add(score, delta);
}
// clear scoreboard and draw initial strings
void init_scoreboard() {
memset((void*)SCORE_BASE, ' ', 1024);
memcpy((void*)SCRNADR(SCORE_BASE,1,24), "SCORE:", 6);
update_scoreboard();
}
byte get_char_for_row(byte row) {
// ground?
if (row >= GROUND_ROW) { return 253; }
// obstacle?
if (row >= GROUND_ROW-3) {
// only show obstacle for certain values of scroll_x
if ((scroll_x & 0b1110000) == 0) { return 247; }
}
// default is the sky (empty space)
return 32;
}
void draw_right_column() {
// get the top-right corner address of scroll area
word addr = SCRNADR(GAME_BASE, 39, SCROLL_TOP);
byte row;
// draw one character per row
for (row=0; row<SCROLL_ROWS; row++) {
POKE(addr, get_char_for_row(row));
addr += 40;
}
}
void scroll_one_column_left() {
// copy several rows of screen memory
// backwards one byte
const word start = SCRNADR(GAME_BASE, 0, SCROLL_TOP);
const word nbytes = SCROLL_ROWS*40-1;
memcpy((byte*)start, (byte*)start+1, nbytes);
// draw the right column of characters
draw_right_column();
}
void scroll_one_pixel_left() {
// scroll left one pixel
scroll_x -= 1;
// set scroll register with lower three bits
VIC.ctrl2 = (VIC.ctrl2 & ~7) | (scroll_x & 7);
// move screen memory if the scroll register
// has just gone past 0 and wrapped to 7
if ((scroll_x & 7) == 7) {
scroll_one_column_left();
}
}
void main() {
// clear screen, set background color
clrscr();
VIC.bgcolor[0] = COLOR_CYAN;
VIC.bordercolor = COLOR_BLUE;
// set vertical scroll = 3, 25 rows
VIC.ctrl1 = 0b00011011;
// set 38 column mode (for X scrolling)
VIC.ctrl2 = 0b00000000;
// set uniform color of characters
memset(COLOR_RAM, COLOR_WHITE, 1000);
// setup scoreboard
init_scoreboard();
// setup rasterirq library for scoreboard split
DLIST_SETUP(display_list);
// game loop, repeat forever
while (1) {
// wait for end of frame
waitvsync();
// scroll screen
scroll_one_pixel_left();
// add to score
add_score(0x0001);
update_scoreboard();
}
}

50
presets/c64/test_setirq.c Normal file
View File

@ -0,0 +1,50 @@
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <peekpoke.h>
#include <string.h>
#include <c64.h>
#include <cbm_petscii_charmap.h>
#include "common.h"
//#link "common.c"
#include <6502.h>
#include <setjmp.h>
char interrupt_handler() {
// only needed if CIA interupts are still active
if (!(VIC.irr & VIC_IRR_IRST)) return IRQ_NOT_HANDLED;
// change colors so we can see where the IRQ fired
VIC.bgcolor0++;
VIC.bordercolor++;
// reading VIC.rasterline returns the current line
// setting it changes the line where the IRQ fires
if (VIC.rasterline >= 245) {
VIC.rasterline = 40;
} else {
VIC.rasterline = 245;
}
// acknowledge VIC raster interrupt (bit 0)
VIC.irr = 1;
// change colors back to where they were
VIC.bgcolor0--;
VIC.bordercolor--;
return IRQ_HANDLED;
}
void main(void) {
clrscr();
printf("\nHello World!\n");
// set interrupt routine
set_irq(interrupt_handler, (void*)0x9f00, 0x100);
// disable CIA interrupt, activate VIC interrupt
set_raster_irq(255);
while (1) {
printf("%d ", VIC.rasterline);
}
}

View File

@ -13,14 +13,15 @@
#include <lz4.h>
// include the LZ4 binary data -> image_c64_multi_lz4[]
//#incbin "image-c64.multi.lz4"
const char image_c64_multi_lz4[] = {
#embed "image-c64.multi.lz4"
};
/*
CharData equ .
ScreenData equ CharData+8000
ColorData equ ScreenData+1000
XtraData equ ColorData+1000
CharData 8000 bytes
ScreenData 1000 bytes
ColorData 1000 bytes
XtraData 2 bytes
*/
void main() {

View File

@ -2,7 +2,6 @@
// ported from
// https://odensskjegg.home.blog/2018/12/29/recreating-the-commodore-64-user-guide-code-samples-in-cc65-part-three-sprites/
//#include "common.h"
#include <peekpoke.h>
#include <c64.h>
@ -71,13 +70,13 @@ int main (void)
rx -= 24;
if (rx >= 0 && rx < 366) {
// Set MSB of x coordinate for sprite if x position > 255
if (x >= 256) {
if (rx >= 256) {
msb |= LUT[t]; // look up 1 << t
}
VIC.spr_pos[t].x = x;
VIC.spr_pos[t].x = rx;
// Y position is an indirect Sinus function of X, using array
// index for retrieving the Y value
VIC.spr_pos[t].y = yValues[x & 63] + 40;
VIC.spr_pos[t].y = yValues[rx & 63] + 40;
} else {
VIC.spr_pos[t].x = 0;
}

71
presets/exidy/minimal.c Normal file

File diff suppressed because one or more lines are too long

View File

@ -79,21 +79,31 @@ typedef enum {
void main();
void start() {
void start() __naked {
__asm
LD SP,#0x4800
EI
; copy initialized data to RAM
LD BC, #l__INITIALIZER+1
LD BC, #l__INITIALIZER
LD A, B
LD DE, #s__INITIALIZED
LD HL, #s__INITIALIZER
LDIR
.skipinit:
JP _main
; padding to get to offset 0x66
.ds 0x66 - (. - _start)
__endasm;
main();
}
const char __at (0x4000) tilerom[0x1000] = {
volatile byte video_framecount = 0; // actual framecount
// starts at address 0x66
void rst_66() __interrupt {
video_framecount++;
}
const char __at (0x4000) tilerom[0x1000] = {/*{w:16,h:16,remap:[3,0,1,2,4,5,6,7,8,9,10],brev:1,np:2,pofs:2048,count:64}*/
0x00,0xfe,0x82,0x82,0x82,0xfe,0xfe,0x00,0x00,0x00,0xfe,0xfe,0xc0,0x00,0x00,0x00,0x00,0xf2,0xf2,0x92,0x92,0x9e,0x9e,0x00,0x00,0xfe,0xfe,0x92,0x92,0x82,0x00,0x00,0x08,0xfe,0xfe,0x88,0x88,0xf8,0xf8,0x00,0x00,0x9e,0x9e,0x92,0x92,0xf2,0xf2,0x00,0x00,0x9e,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xf0,0xf0,0x9e,0x9e,0x80,0x80,0x00,0x00,0xfe,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0xfe,0x92,0x92,0x92,0xf2,0xf0,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x80,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xc8,0x88,0x88,0xfe,0xfe,0x00,0x00,0xee,0x92,0x92,0x92,0xfe,0xfe,0x00,0x00,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,0x00,0xfc,0x86,0x82,0x82,0xfe,0xfe,0x00,0x00,0x82,0x92,0x92,0x92,0xfe,0xfe,0x00,0x80,0x90,0x90,0x90,0x90,0xfe,0xfe,0x00,0x00,0x9e,0x92,0x82,0x82,0xfe,0xfe,0x00,0xfe,0xfe,0x10,0x10,0x10,0xfe,0xfe,0x00,0x00,0x00,0xbe,0xbe,0x00,0x00,0x00,0x00,0xfc,0xfe,0x06,0x02,0x02,0x02,0x00,0x00,0x00,0x82,0x44,0x28,0x18,0xfe,0xfe,0x00,0x02,0x02,0x02,0x06,0xfe,0xfe,0x00,0x00,0xfe,0x40,0x20,0x18,0x20,0xfe,0xfe,0x00,0xfe,0x0c,0x08,0x10,0x20,0xfe,0xfe,0x00,0xfe,0x82,0x82,0x82,0x86,0xfe,0xfe,0x00,
0x00,0xf8,0x88,0x88,0x88,0xfe,0xfe,0x00,0x7e,0x86,0x8a,0x82,0x82,0xfe,0xfe,0x00,0xf8,0x8a,0x8c,0x88,0x88,0xfe,0xfe,0x00,0x00,0x9e,0x96,0x92,0x92,0xf2,0xf2,0x00,0x80,0x80,0xfe,0xfe,0x80,0x80,0x00,0x00,0x00,0xfe,0x06,0x02,0x02,0xfe,0xfe,0x00,0xf0,0x08,0x04,0x06,0x0c,0xf8,0xf0,0x00,0xf8,0x06,0x0c,0x18,0x0c,0xfe,0xf8,0x00,0x82,0x44,0x28,0x38,0x6c,0xc6,0x82,0x00,0x80,0x40,0x30,0x1e,0x3e,0x40,0x80,0x00,0xc2,0xe2,0xb2,0x9e,0x8e,0x86,0x82,0x00,0x00,0x10,0x10,0x10,0x10,0x10,0x10,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,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,0x40,0x80,0x80,0x78,0x7e,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x78,0x80,0x80,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x28,0x07,0x07,0x03,0x00,0x00,0x00,0x00,0x00,0x80,0xe0,0xf0,0x07,0x07,0x28,0x10,0x00,0x00,0x00,0x00,0xe0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x04,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x80,0xe0,0xe0,0x07,0x27,0x18,0x00,0x00,0x00,0x00,0x00,0xf0,0xf8,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x04,0x02,0x03,0x07,0x27,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xe0,0x3f,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xf0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x01,0x01,0x01,0x23,0x3f,0x07,0x00,0x00,0x00,0x00,0x80,0xc0,0xc0,0xe0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x13,0x1f,0x07,0x03,0x00,0x00,0x80,0x80,0x80,0xc0,0xe0,0xf0,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xe0,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x10,0x09,0x0f,0x07,0x07,0x00,0x80,0x40,0x80,0x80,0xc0,0xc0,0xc0,0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x08,0x04,0x03,0x03,0x03,0x00,0x00,0x10,0x08,0x10,0x60,0xe0,0xe0,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0xe0,0xc0,0xc0,0x80,0x00,0x00,0x00,0x00,
@ -112,7 +122,8 @@ const char __at (0x4000) tilerom[0x1000] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x78,0x00,0x02,0x06,0x10,0x01,0x4a,0x00,0x10,0x00,0xa0,0x02,0x14,0x10,0x08,0x00,0x00,0x80,0x01,0x1c,0x40,0x86,0x01,0x81,0x60,0x00,0x04,0x90,0x70,0x08,0x02,0x00,0x40,0x00,0x00,0x40,0x00,0x20,0x00,0x10,0x7c,0x81,0x01,0x01,0x04,0x00,0x08,0x80,0x20,0x04,0x01,0x40,0x04,0x48,0x11,0xa0,0x00,0x02,0x00,0x08,0x84,0x02,0x01,0x00,0x18,0x86,0x80,0x0c,0x11,0x18,0xdb,0x05,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x02,0x00,0x00,0x00,0x00,0x62,0x90,0x01,0x05,0x00,0x01,0x81,0x7c,0x01,0x40,0x00,0xc0,0x00,0x01,0x80,0x80,0x40,0x44,0x80,0x01,0x02,0x64,0x00,0x00,0x48,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x04,0x03,0x00,0x00,0x18,0x00,0x12,0x10,0x0a,0xc2,0x00,0x01,0x00,0x00,0x01,0x00,0x02,0x00,0x21,0x02,0x19,0x8e,0xa2,0x6c,0xe4,0x70,0x00,0x00,0x00,0x00,0x00,0x60,0xe3,0x02,0x60,0x00,0x14,0x20,0x08,0x80,0x00,0x00,0x0e,0x5a,0x5b,0x8a,0x45,0x35,0x04,0x02,0xe0,0x00,0x10,0x90,0x40,0x20,0x00,0x80,0x12,0x15,0x04,0x00,0x00,0x00,0x02,0x02,0xc0,0x60,0x20,0x60,0x20,0x18,0x3c,0x00,0x01,0x0e,0x0a,0x10,0x18,0x10,0x00,0x30,0x05,0x20,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x09,0x08,0x08,0x40,0x40,0x0c,0x10,0x20,0x20,0xd0,0x00,0x08,0x70,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x24,0x12,0x0a,0x00,0x10,
0x00,0x00,0x60,0x30,0x18,0x08,0x00,0x00,0x10,0x40,0x20,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x20,0x20,0x00,0x00,0x00,0x40,0x10,0x08,0x0d,0x10,0x00,0x00,0x00,0x00,0x06,0x10,0x00,0x00,0x00,0x05,0x08,0x02,0x44,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0xa0,0x10,0x88,0x04,0x00,0x00,0x00,0x00,0x00,0x06,0x04,0x00,0x00,0x00,0x08,0x08,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x00,0x00,0x00,0x08,0x12,0x24,0x08,0x50,0x00,0x40,0x00,0x00,0x70,0x04,0x00,0x01,0x0e,0x80,0xa8,0x00,0x80,0x20,0x40,0x60,0x00,0x80,0x08,0x00,0x08,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x02,0x00,0x00,0x88,0x00,0x40,0x00,0x39,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x40,0x60,0x38,0x01,0x08,0x04,0x02,0x00,0x30,0x30,0x18,0x04,0x00,0x01,0x00,0x0a,0x0c,0x04,0x02,0x02,0x04,0x70,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xe0,0xe5,0x03,0x00,0x00,0x00,0x00,0x0c,0x18,0x40,0x80,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x3a,0x00,0x08,0x98,0x70,0x00,0x20,0x0e,0x0e,0x01,0x00,0x00,0x02,0x80,0x90,0x23,0x31,0xbd,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x02,0x80,0x87,0x08,0x10,0x20,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x80,0x00,0x00,0x00,0x40,0x00,0x40,0x10,0x64,0x84,0x00,0x00,0x00,0x00,0x00,0x10,0xc8,0xc1,0xc0,0x38,0x20,0x80,0x50,0x10,0x08,0x00,0x00,0x80,
};
const char __at (0x5000) palette[32] = {
const char __at (0x5000) palette[32] = {/*{pal:332,n:4}*/
0x00,0x00,0x00,0xf6,0x00,0x16,0xc0,0x3f,
0x00,0xd8,0x07,0x3f,0x00,0xc0,0xc4,0x07,
0x00,0xc0,0xa0,0x07,0x00,0x00,0x00,0x07,
@ -134,30 +145,6 @@ byte getchar(byte x, byte y) {
return vram[29-x][y];
}
volatile byte video_framecount; // actual framecount
void _buffer() {
__asm
; padding to get to offset 0x66
ld ix,#0
ld ix,#0
ld ix,#0
nop
nop
nop
nop
__endasm;
}
void rst_66() __interrupt {
video_framecount++;
}
void reset_video_framecount() __critical {
video_framecount = 0;
}
void putchar(byte x, byte y, byte ch) {
vram[29-x][y] = ch;
}
@ -176,9 +163,10 @@ void putstring(byte x, byte y, const char* string) {
}
void wait_for_frame() {
byte initial_framecount = video_framecount;
watchdog++;
while (video_framecount == initial_framecount);
__asm
HALT
__endasm;
}
///

View File

@ -0,0 +1,67 @@
#include <atari2600.h>
#include <vcslib.h>
#include <peekpoke.h>
#include <mapper.h>
#ifdef __ATARI2600_MAPPER_3E__
MAPPER_CART_ROM_KB(6); // 6K ROM
#endif
#ifdef MAPPER_BANKED_ROM
#define ROM_BANK(index) __attribute__((noinline, section(".rom"#index)))
#else
#define ROM_BANK(index)
#endif
#define KERNEL_BANK 1
ROM_BANK(KERNEL_BANK) void my_preframe(void) {
}
ROM_BANK(KERNEL_BANK) void my_doframe(void) {
int i;
// Set player sprite color
TIA.colup0 = COLOR_CONV(0xfe);
// Draw each scanline
for (i=0; i<192; i++) {
TIA.wsync = 0; // sync to scanline
TIA.colubk = i; // set background color
TIA.pf1 = i; // set playfield
TIA.grp0 = i; // set sprite bitmap
}
TIA.grp0 = 0; // clear sprite
TIA.colubk = 0; // clear background
}
ROM_BANK(KERNEL_BANK) void my_postframe(void) {
// additional post-frame processing goes here
}
// Display kernel loop
ROM_BANK(KERNEL_BANK) void do_kernel_loop() {
// loop until reset released
while (SW_RESET()) { }
// loop forever
while (1) {
kernel_1();
my_preframe();
kernel_2();
my_doframe();
kernel_3();
my_postframe();
kernel_4();
}
}
int main() {
// test banked rom call, if available
#ifdef MAPPER_BANKED_ROM
banked_call_rom(KERNEL_BANK, do_kernel_loop);
#else
do_kernel_loop();
#endif
return 0;
}

View File

@ -161,8 +161,8 @@ extern void tinyfont48_build(byte* dest, const char str[12]);
#define OVERSCAN_TIM64 _TIM64(_CYCLES(36))
#else
#define VBLANK_TIM64 _TIM64(_CYCLES(37))
#define KERNAL_TIM64 _TIM64(_CYCLES(194))
#define OVERSCAN_TIM64 _TIM64(_CYCLES(32))
#define KERNAL_TIM64 _TIM64(_CYCLES(198))
#define OVERSCAN_TIM64 _TIM64(_CYCLES(28))
#endif
#define JOY_UP(plyr) (!(RIOT.swcha & ((plyr) ? 0x1 : ~MOVE_UP)))

View File

@ -0,0 +1,5 @@
org $a000-2 ; so we can write the ...
.word $a000 ; cartridge 2-byte header
.word Start ; start vector
.word Start ; RESTORE vector
.byte $41, $30, $c3, $c2, $cd ; "A0CBM"

View File

@ -4,11 +4,11 @@
#include "stdlib.h"
const byte palette_data[16] = {
0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x00, 0x00, 0x00, 0xf8, };
const byte palette_data[16] = {/*{pal:332,n:16}*/
0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, };
const byte sprite1[2+16*16/2] = {
8,16,
8,16,/*{w:16,h:16,bpp:4,brev:1}*/
0x00,0x09,0x99,0x00,0x00,0x99,0x90,0x00,
0x00,0x94,0x94,0x90,0x09,0x49,0x49,0x00,
0x04,0x49,0x49,0x90,0x09,0x94,0x94,0x90,
@ -27,27 +27,28 @@ const byte sprite1[2+16*16/2] = {
0x00,0x09,0x99,0x00,0x00,0x99,0x90,0x00,
};
const byte sprite2[2+16*15/2] = {
8,16,
0x00,0x94,0x94,0x90,0x09,0x49,0x49,0x00,
0x04,0x49,0x49,0x90,0x09,0x94,0x94,0x90,
0x94,0x99,0x94,0x90,0x09,0x49,0x99,0x49,
0x99,0x99,0x49,0x93,0x39,0x94,0x99,0x99,
0x04,0x49,0x99,0x94,0x49,0x99,0x94,0x90,
0x00,0x94,0x94,0x43,0x34,0x49,0x49,0x00,
0x00,0x09,0x43,0x94,0x49,0x34,0x90,0x00,
0x00,0x90,0x00,0x39,0x93,0x00,0x09,0x00,
0x00,0x09,0x83,0x33,0x33,0x33,0x90,0x00,
0x00,0x09,0x32,0x23,0x32,0x23,0x90,0x00,
0x00,0x03,0x03,0x23,0x82,0x30,0x30,0x00,
0x03,0x30,0x00,0x33,0x33,0x00,0x03,0x30,
0x00,0x30,0x03,0x00,0x00,0x30,0x03,0x00,
0x00,0x00,0x00,0x30,0x03,0x00,0x00,0x00,
0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,
const byte sprite2[2+16*16/2] = {
8,16,/*{w:16,h:16,bpp:4,brev:1}*/
0x00,0x08,0x80,0x40,0x00,0x00,0x8D,0x00,
0x80,0x94,0x88,0x90,0x09,0x48,0x89,0x00,
0x08,0x49,0x48,0x66,0x66,0x98,0x94,0x98,
0x94,0x89,0x66,0x66,0x66,0x66,0x98,0x89,
0x99,0x66,0x67,0x76,0x67,0x76,0x69,0x98,
0x88,0x67,0x77,0x66,0x66,0x77,0x74,0x90,
0x09,0x97,0x74,0x46,0x64,0x47,0x78,0x88,
0x09,0x89,0x43,0x96,0x69,0x34,0x99,0x90,
0x08,0x89,0xBB,0x39,0x93,0xBB,0x98,0x90,
0x80,0x99,0x3B,0x33,0x33,0xB0,0x99,0x88,
0x00,0x09,0x9C,0xC3,0x3C,0xC8,0x99,0x08,
0x00,0x00,0x03,0xC3,0x8C,0x30,0x00,0x00,
0x00,0x00,0x0D,0xD3,0x3D,0x00,0x00,0x00,
0x00,0x00,0x0D,0x00,0x0D,0xD0,0x00,0x00,
0x00,0x00,0xDD,0x30,0x03,0xD0,0x00,0x00,
0x00,0x00,0xD0,0x40,0x00,0xD0,0x00,0x00,
};
const byte sprite3[2+16*16/2] = {
8,16,
8,16,/*{w:16,h:16,bpp:4,brev:1}*/
0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,
0x00,0x00,0x00,0x11,0x11,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,
@ -67,7 +68,7 @@ const byte sprite3[2+16*16/2] = {
};
const byte sprite4[2+16*16/2] = {
8,16,
8,16,/*{w:16,h:16,bpp:4,brev:1}*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xbb,0xbb,0x00,0x00,0x00,
0x00,0x00,0x00,0xbb,0xbb,0x00,0x00,0x00,
@ -106,8 +107,8 @@ const byte sprite5[2+16*16/2] = {
0x02,0x02,0x00,0x20,0x02,0x00,0x20,0x20,
};
const byte sprite6[2+16*10/2] = {
8,10,
const byte sprite6[2+10*16/2] = {
8,10,/*{w:16,h:10,bpp:4,brev:1}*/
0x00,0x00,0x00,0x00,0x04,0x04,0x04,0x00,
0x00,0x00,0x00,0x00,0x44,0x44,0x44,0x40,
0x00,0x00,0x04,0x04,0x49,0x49,0x99,0x44,
@ -120,8 +121,8 @@ const byte sprite6[2+16*10/2] = {
0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
const byte sprite7[2+16*14/2] = {
8,14,
const byte sprite7[2+14*16/2] = {
8,14,/*{w:16,h:14,bpp:4,brev:1}*/
0x00,0x60,0x00,0x00,0x00,0x00,0x06,0x00,
0x60,0x60,0x00,0x00,0x00,0x00,0x06,0x06,
0x60,0x60,0x50,0x00,0x00,0x05,0x06,0x06,
@ -138,8 +139,8 @@ const byte sprite7[2+16*14/2] = {
0x00,0x00,0x10,0x00,0x00,0x01,0x00,0x00,
};
const byte sprite8[2+16*15/2] = {
8,15,
const byte sprite8[2+15*16/2] = {
8,15,/*{w:16,h:15,bpp:4,brev:1}*/
0x00,0x00,0x11,0x11,0x11,0x11,0x00,0x00,
0x00,0x01,0x61,0x11,0x21,0x12,0x10,0x00,
0x00,0x11,0x12,0x11,0x61,0x11,0x11,0x00,
@ -158,7 +159,7 @@ const byte sprite8[2+16*15/2] = {
};
const byte sprite9[2+13*16/2] = {
8,13,
8,13,/*{w:16,h:13,bpp:4,brev:1}*/
0x00,0x00,0xaa,0x00,0x00,0xaa,0x00,0x00,
0x00,0x00,0xa0,0x0a,0xa0,0x0a,0x00,0x00,
0x0a,0xaa,0xa4,0xaa,0xaa,0x3a,0xaa,0xa0,
@ -174,6 +175,7 @@ const byte sprite9[2+13*16/2] = {
0x00,0x0a,0xa0,0x00,0x00,0x0a,0xa0,0x00,
};
const byte* const all_sprites[9] = {
sprite1,
sprite2,
@ -329,6 +331,7 @@ int main() {
while (video_counter >= 0x80) ;
update_grid_rows(GDIM/2,GDIM);
watchdog0x39 = 0x39;
//palette[0] = i++;
}
return 0;
}

View File

@ -1,30 +1,30 @@
import { hex, byte2signed } from "./util";
import { Platform } from "./baseplatform";
import { OpcodeMetadata, Platform } from "./baseplatform";
const debug = false;
export interface CodeAnalyzer {
showLoopTimingForPC(pc:number);
pc2clockrange : {[key:number]:ClockRange};
MAX_CLOCKS : number;
showLoopTimingForPC(pc: number);
pc2clockrange: { [key: number]: ClockRange };
MAX_CLOCKS: number;
}
/// VCS TIMING ANALYSIS
// [taken, not taken]
const BRANCH_CONSTRAINTS = [
[{N:0},{N:1}],
[{N:1},{N:0}],
[{V:0},{V:1}],
[{V:1},{V:0}],
[{C:0},{C:1}],
[{C:1},{C:0}],
[{Z:0},{Z:1}],
[{Z:1},{Z:0}]
[{ N: 0 }, { N: 1 }],
[{ N: 1 }, { N: 0 }],
[{ V: 0 }, { V: 1 }],
[{ V: 1 }, { V: 0 }],
[{ C: 0 }, { C: 1 }],
[{ C: 1 }, { C: 0 }],
[{ Z: 0 }, { Z: 1 }],
[{ Z: 1 }, { Z: 0 }]
];
function constraintEquals(a,b) {
function constraintEquals(a, b) {
if (a == null || b == null)
return null;
for (var n in a) {
@ -44,15 +44,15 @@ interface ClockRange {
}
abstract class CodeAnalyzer6502 implements CodeAnalyzer {
pc2clockrange : {[key:number]:ClockRange} = {};
jsrresult : {[key:number]:ClockRange} = {};
START_CLOCKS : number;
MAX_CLOCKS : number;
WRAP_CLOCKS : boolean;
platform : Platform;
MAX_CYCLES : number = 2000;
constructor(platform : Platform) {
pc2clockrange: { [key: number]: ClockRange } = {};
jsrresult: { [key: number]: ClockRange } = {};
START_CLOCKS: number;
MAX_CLOCKS: number;
WRAP_CLOCKS: boolean;
platform: Platform;
MAX_CYCLES: number = 2000;
constructor(platform: Platform) {
this.platform = platform;
}
@ -62,12 +62,12 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
return meta; // minCycles, maxCycles
}
traceInstructions(pc:number, minclocks:number, maxclocks:number, subaddr:number, constraints) {
traceInstructions(pc: number, minclocks: number, maxclocks: number, subaddr: number, constraints) {
if (debug) console.log("trace", hex(pc), minclocks, maxclocks);
if (!constraints) constraints = {};
var modified = true;
var abort = false;
for (let i=0; modified && !abort; i++) {
for (let i = 0; modified && !abort; i++) {
if (i >= this.MAX_CYCLES) {
console.log("too many cycles @", hex(pc), "routine", hex(subaddr));
break;
@ -77,10 +77,10 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
// wrap clocks
minclocks = minclocks % this.MAX_CLOCKS;
maxclocks = maxclocks % this.MAX_CLOCKS;
if (maxclocks == minclocks-1) {
if (maxclocks == minclocks - 1) {
if (debug) console.log("0-75", hex(pc), minclocks, maxclocks);
minclocks = 0;
maxclocks = this.MAX_CLOCKS-1;
maxclocks = this.MAX_CLOCKS - 1;
}
} else {
// truncate clocks
@ -88,13 +88,13 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
maxclocks = Math.min(this.MAX_CLOCKS, maxclocks);
}
let meta = this.getClockCountsAtPC(pc);
let lob = this.platform.readAddress(pc+1);
let hib = this.platform.readAddress(pc+2);
let lob = this.platform.readAddress(pc + 1);
let hib = this.platform.readAddress(pc + 2);
let addr = lob + (hib << 8);
let pc0 = pc;
let pcrange = this.pc2clockrange[pc0];
if (pcrange == null) {
this.pc2clockrange[pc0] = pcrange = {minclocks:minclocks, maxclocks:maxclocks};
this.pc2clockrange[pc0] = pcrange = { minclocks: minclocks, maxclocks: maxclocks };
if (debug) console.log("new", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks);
modified = true;
}
@ -103,7 +103,7 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
if (this.WRAP_CLOCKS && (minclocks <= maxclocks) != (pcrange.minclocks <= pcrange.maxclocks)) {
if (debug) console.log("wrap", hex(pc), hex(pc0), hex(subaddr), minclocks, maxclocks, pcrange);
pcrange.minclocks = minclocks = 0;
pcrange.maxclocks = maxclocks = this.MAX_CLOCKS-1;
pcrange.maxclocks = maxclocks = this.MAX_CLOCKS - 1;
modified = true;
}
if (minclocks < pcrange.minclocks) {
@ -124,106 +124,88 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
pc += meta.insnlength;
var oldconstraints = constraints;
constraints = null;
// TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) {
case 0x19: case 0x1d:
case 0x39: case 0x3d:
case 0x59: case 0x5d:
case 0x79: case 0x7d:
case 0xb9: case 0xbb:
case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xd9: case 0xdd:
case 0xf9: case 0xfd:
if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed
break;
// TODO: only VCS
case 0x85:
if (lob == 0x2) { // STA WSYNC
minclocks = maxclocks = 0;
meta.minCycles = meta.maxCycles = 0;
}
break;
// TODO: only NES (sprite 0 poll)
case 0x2c:
if (lob == 0x02 && hib == 0x20) { // BIT $2002
minclocks = 0;
maxclocks = 4; // uncertainty b/c of assumed branch poll
meta.minCycles = meta.maxCycles = 0;
}
break;
// TODO: only Apple2 (vapor lock)
/*
case 0xad:
if (lob == 0x61 && hib == 0xc0) { // LDA $C061
minclocks = 0;
maxclocks = 4; // uncertainty?
meta.minCycles = meta.maxCycles = 0;
}
break;
*/
case 0x20: // JSR
// TODO: handle bare RTS case
minclocks += meta.minCycles;
maxclocks += meta.maxCycles;
this.traceInstructions(addr, minclocks, maxclocks, addr, constraints);
var result = this.jsrresult[addr];
if (result) {
minclocks = result.minclocks;
maxclocks = result.maxclocks;
} else {
console.log("No JSR result!", hex(pc), hex(addr));
minclocks = maxclocks;
//return;
}
break;
case 0x4c: // JMP
pc = addr; // TODO: make sure in ROM space
break;
case 0x40: // RTI
abort = true;
break;
case 0x60: // RTS
if (subaddr) { // TODO: 0 doesn't work
// TODO: combine with previous result
var result = this.jsrresult[subaddr];
if (!result) {
result = {minclocks:minclocks, maxclocks:maxclocks};
let syncMaxCycles = this.getMaxCyclesForSync(meta, lob, hib);
if (typeof syncMaxCycles === 'number') {
minclocks = 0;
maxclocks = syncMaxCycles;
meta.minCycles = meta.maxCycles = 0;
} else {
// TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) {
case 0x19: case 0x1d:
case 0x39: case 0x3d:
case 0x59: case 0x5d:
case 0x79: case 0x7d:
case 0xb9: case 0xbb:
case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xd9: case 0xdd:
case 0xf9: case 0xfd:
if (lob == 0) meta.maxCycles -= 1; // no page boundary crossed
break;
case 0x20: // JSR
// TODO: handle bare RTS case
minclocks += meta.minCycles;
maxclocks += meta.maxCycles;
this.traceInstructions(addr, minclocks, maxclocks, addr, constraints);
var result = this.jsrresult[addr];
if (result) {
minclocks = result.minclocks;
maxclocks = result.maxclocks;
} else {
result = {
minclocks:Math.min(minclocks,result.minclocks),
maxclocks:Math.max(maxclocks,result.maxclocks)
}
console.log("No JSR result!", hex(pc), hex(addr));
minclocks = maxclocks;
//return;
}
this.jsrresult[subaddr] = result;
console.log("RTS", hex(pc), hex(subaddr), this.jsrresult[subaddr]);
}
return;
case 0x10: case 0x30: // branch
case 0x50: case 0x70:
case 0x90: case 0xB0:
case 0xD0: case 0xF0:
var newpc = pc + byte2signed(lob);
var crosspage = (pc>>8) != (newpc>>8);
if (!crosspage) meta.maxCycles--;
// TODO: other instructions might modify flags too
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode-0x10)/0x20)];
var cons0 = constraintEquals(oldconstraints, cons[0]);
var cons1 = constraintEquals(oldconstraints, cons[1]);
// recursively trace the taken branch
if (true || cons0 !== false) { // TODO?
this.traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
}
// abort if we will always take the branch
if (cons1 === false) {
console.log("branch always taken", hex(pc), oldconstraints, cons[1]);
break;
case 0x4c: // JMP
pc = addr; // TODO: make sure in ROM space
break;
case 0x40: // RTI
abort = true;
}
constraints = cons[1]; // not taken
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
break;
case 0x6c:
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
return;
break;
case 0x60: // RTS
if (subaddr) { // TODO: 0 doesn't work
// TODO: combine with previous result
var result = this.jsrresult[subaddr];
if (!result) {
result = { minclocks: minclocks, maxclocks: maxclocks };
} else {
result = {
minclocks: Math.min(minclocks, result.minclocks),
maxclocks: Math.max(maxclocks, result.maxclocks)
}
}
this.jsrresult[subaddr] = result;
console.log("RTS", hex(pc), hex(subaddr), this.jsrresult[subaddr]);
}
return;
case 0x10: case 0x30: // branch
case 0x50: case 0x70:
case 0x90: case 0xB0:
case 0xD0: case 0xF0:
var newpc = pc + byte2signed(lob);
var crosspage = (pc >> 8) != (newpc >> 8);
if (!crosspage) meta.maxCycles--;
// TODO: other instructions might modify flags too
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode - 0x10) / 0x20)];
var cons0 = constraintEquals(oldconstraints, cons[0]);
var cons1 = constraintEquals(oldconstraints, cons[1]);
// recursively trace the taken branch
if (true || cons0 !== false) { // TODO?
this.traceInstructions(newpc, minclocks + meta.maxCycles, maxclocks + meta.maxCycles, subaddr, cons[0]);
}
// abort if we will always take the branch
if (cons1 === false) {
console.log("branch always taken", hex(pc), oldconstraints, cons[1]);
abort = true;
}
constraints = cons[1]; // not taken
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
break;
case 0x6c:
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
return;
}
}
// add min/max instruction time to min/max clocks bound
if (debug) console.log("add", hex(pc), meta.minCycles, meta.maxCycles);
@ -232,41 +214,65 @@ abstract class CodeAnalyzer6502 implements CodeAnalyzer {
}
}
showLoopTimingForPC(pc:number) {
showLoopTimingForPC(pc: number) {
this.pc2clockrange = {};
this.jsrresult = {};
// recurse through all traces
this.traceInstructions(pc | this.platform.getOriginPC(), this.START_CLOCKS, this.MAX_CLOCKS, 0, {});
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
}
}
// 76 cycles
export class CodeAnalyzer_vcs extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 76; // 1 scanline
this.START_CLOCKS = 0; // TODO?
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0x85) {
if (lob == 0x2) { // STA WSYNC
return 0;
}
}
}
}
// https://wiki.nesdev.com/w/index.php/PPU_rendering#Line-by-line_timing
// TODO: sprite 0 hit, CPU stalls
export class CodeAnalyzer_nes extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 114; // 341 clocks for 3 scanlines
this.START_CLOCKS = 0;
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0x2c) {
if (lob == 0x02 && hib == 0x20) { // BIT $2002
return 4; // uncertainty b/c of assumed branch poll
}
}
}
}
export class CodeAnalyzer_apple2 extends CodeAnalyzer6502 {
constructor(platform : Platform) {
constructor(platform: Platform) {
super(platform);
this.MAX_CLOCKS = 65;
this.START_CLOCKS = 0;
this.WRAP_CLOCKS = true;
}
getMaxCyclesForSync(meta: OpcodeMetadata, lob: number, hib: number) {
if (meta.opcode == 0xad) {
if (lob == 0x61 && hib == 0xc0) { // LDA $C061
return 4; // uncertainty b/c of assumed branch poll
}
}
}
}

View File

@ -1,5 +1,5 @@
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller } from "./emu";
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller, drawCrosshair } from "./emu";
import { hex, printFlags, invertMap, byteToASCII } from "./util";
import { CodeAnalyzer } from "./analysis";
import { Segment, FileData } from "./workertypes";
@ -158,6 +158,7 @@ export interface Preset {
name : string;
chapter? : number;
title? : string;
category?: string;
}
export interface MemoryBus {
@ -850,7 +851,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
}
}
}
loadROM(title, data) {
this.machine.loadROM(data, title);
this.reset();
@ -872,10 +873,31 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
advance(novideo:boolean) {
let trap = this.getDebugCallback();
var steps = this.machine.advanceFrame(trap);
if (!novideo && this.video) this.video.updateFrame();
if (!novideo && this.serialVisualizer) this.serialVisualizer.refresh();
return steps;
try {
var steps = this.machine.advanceFrame(trap);
return steps;
} finally {
// in case EmuHalt is thrown...
if (!novideo && this.video) {
this.video.updateFrame();
this.updateVideoDebugger();
}
if (!novideo && this.serialVisualizer) {
this.serialVisualizer.refresh();
}
}
}
updateVideoDebugger() {
if (!this.isRunning() && isRaster(this.machine) && this.machine.getRasterCanvasPosition) {
const {x,y} = this.machine.getRasterCanvasPosition();
if (x >= 0 || y >= 0) {
const ctx = this.video?.getContext();
if (ctx) {
drawCrosshair(ctx, x, y, 1);
}
}
}
}
advanceFrameClock(trap, step) {
@ -915,7 +937,10 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
// TODO: reset target clock counter
getRasterScanline() {
return isRaster(this.machine) && this.machine.getRasterY();
return isRaster(this.machine) && this.machine.getRasterY ? this.machine.getRasterY() : -1;
}
getRasterLineClock() {
return isRaster(this.machine) && this.machine.getRasterX ? this.machine.getRasterX() : -1;
}
readAddress(addr : number) : number {
@ -936,7 +961,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
getOpcodeMetadata = getOpcodeMetadata_6502;
getToolForFilename = getToolForFilename_6502;
getToolForFilename(fn) { return getToolForFilename_6502(fn); }
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));

1257
src/common/binutils.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import { Bus, CPU, InstructionBased, SavesState } from "../devices";
import { Bus, Bus32, CPU, InstructionBased, SavesState } from "../devices";
import { EmuHalt } from "../emu";
import { hex } from "../util";
@ -103,12 +103,18 @@ export interface ARMCoreState {
bankedRegisters: number[][],
spsr: number,
bankedSPSRs: number[],
sfprs: number[],
dfprs: number[],
ifprs: number[],
cycles: number,
instructionWidth: 2 | 4
}
interface ARMCoreType {
gprs: Int32Array;
sfprs: Float32Array;
dfprs: Float64Array;
ifprs: Int32Array;
PC: number;
SP: number;
LR: number;
@ -659,6 +665,11 @@ ARMCoreArm.prototype.constructAddressingMode4Writeback = function(immediate, off
}
};
ARMCoreArm.prototype.constructNOP = function() {
this.writesPC = false;
return function() { };
}
ARMCoreArm.prototype.constructADC = function(rd, rn, shiftOp, condOp) {
var cpu : ARMCoreType = this.cpu;
var gprs = cpu.gprs;
@ -1760,6 +1771,265 @@ ARMCoreArm.prototype.constructUMULLS = function(rd, rn, rs, rm, condOp) {
};
};
ARMCoreArm.prototype.constructVFP3Register = function(condOp, opcode, nOperandReg, destReg, sz, opcode2, mOperandReg) {
var cpu : ARMCoreType = this.cpu;
var fpregs = sz ? cpu.dfprs : cpu.sfprs;
//console.log("VFP3Register: " + hex(opcode) + " " + hex(nOperandReg) + " " + hex(destReg) + " " + hex(number) + " " + hex(opcode2) + " " + hex(mOperandReg));
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
switch (opcode) {
case 2: // VMUL
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] * fpregs[mOperandReg];
return;
}
break;
case 3: // VADD/VSUB
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] + fpregs[mOperandReg];
return;
case 2:
fpregs[destReg] = fpregs[nOperandReg] - fpregs[mOperandReg];
return;
}
break;
case 8: // VDIV
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] / fpregs[mOperandReg];
return;
}
break;
}
console.log("Unsupported instruction: " + hex(opcode) + " " + hex(opcode2));
};
};
/*
if ConditionPassed() then
EncodingSpecificOperations(); CheckVFPEnabled(TRUE);
if to_integer then
if dp_operation then
S[d] = FPToFixed(D[m], 32, 0, unsigned, round_zero, TRUE);
else
S[d] = FPToFixed(S[m], 32, 0, unsigned, round_zero, TRUE);
else
if dp_operation then
D[d] = FixedToFP(S[m], 64, 0, unsigned, round_nearest, TRUE);
else
S[d] = FixedToFP(S[m], 32, 0, unsigned, round_nearest, TRUE);
*/
ARMCoreArm.prototype.constructVCVT = function(condOp, d, m, to_integer, dp_operation, unsigned, round_zero, round_nearest) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
var src : number;
var dest : number;
// get source
if (to_integer && dp_operation) {
src = dregs[m];
} else if (to_integer) {
src = sregs[m];
} else {
src = iregs[m];
}
// convert
if (to_integer) {
dest = round_zero ? Math.floor(src) : Math.round(src);
} else {
dest = src;
}
// store result
if (to_integer) {
iregs[d] = dest;
} else if (dp_operation) {
dregs[d] = dest;
} else {
sregs[d] = dest;
}
};
}
ARMCoreArm.prototype.constructVCVTF = function(condOp, d, m, double_to_single) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
var n = double_to_single ? dregs[m] : sregs[m];
// store result
if (double_to_single) {
sregs[d] = n;
} else {
dregs[d] = n;
}
};
}
ARMCoreArm.prototype.constructVLDR = function(condOp, destReg, address, single_reg) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = address();
if (single_reg) {
iregs[destReg] = cpu.mmu.load32(addr);
} else {
iregs[destReg] = cpu.mmu.load32(addr);
iregs[destReg+1] = cpu.mmu.load32(addr+4);
}
cpu.mmu.wait32(addr);
cpu.mmu.wait32(cpu.gprs[ARMRegs.PC]);
};
};
ARMCoreArm.prototype.constructVSTR = function(condOp, srcReg, address, single_reg) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = address();
if (single_reg) {
cpu.mmu.store32(addr, iregs[srcReg]);
} else {
cpu.mmu.store32(addr, iregs[srcReg]);
cpu.mmu.store32(addr+4, iregs[srcReg+1]);
}
cpu.mmu.wait32(addr);
cpu.mmu.wait32(cpu.gprs[ARMRegs.PC]);
};
}
ARMCoreArm.prototype.constructVPUSH = function(condOp, d, regs, single_regs) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = cpu.gprs[ARMRegs.SP] - regs * 4;
cpu.gprs[ARMRegs.SP] = addr;
for (let i = 0; i < regs; ++i) {
cpu.mmu.store32(addr, iregs[d+i]);
addr += 4;
}
};
}
ARMCoreArm.prototype.constructVPOP = function(condOp, d, regs, single_regs) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = cpu.gprs[ARMRegs.SP];
cpu.gprs[ARMRegs.SP] += regs * 4;
for (let i = 0; i < regs; ++i) {
iregs[d+i] = cpu.mmu.load32(addr);
addr += 4;
}
};
}
function FPCompare(op1: number, op2: number) {
/* assert N IN {32,64};
fpscr_val = if fpscr_controlled then FPSCR else StandardFPSCRValue();
(type1,sign1,value1) = FPUnpack(op1, fpscr_val);
(type2,sign2,value2) = FPUnpack(op2, fpscr_val); */
if (isNaN(op1) || isNaN(op2)) {
return 0b0011;
}
if (op1 == op2) return 0b0110;
if (op1 < op2) return 0b1000;
else return 0b0010;
}
ARMCoreArm.prototype.constructVCMP = function(condOp, d, Vd, sz, E, m, Vm) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let op1, op2;
if (sz) {
op1 = dregs[d];
op2 = dregs[m];
} else {
op1 = sregs[d];
op2 = sregs[m];
}
let result = FPCompare(op1, op2);
cpu.cpsrN = (result & 8) != 0;
cpu.cpsrZ = (result & 4) != 0;
cpu.cpsrC = (result & 2) != 0;
cpu.cpsrV = (result & 1) != 0;
}
}
ARMCoreArm.prototype.constructVCMP0 = function(condOp, d, Vd, sz, E) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let op1, op2=0;
if (sz) {
op1 = dregs[d];
} else {
op1 = sregs[d];
}
let result = FPCompare(op1, op2);
cpu.cpsrN = (result & 8) != 0;
cpu.cpsrZ = (result & 4) != 0;
cpu.cpsrC = (result & 2) != 0;
cpu.cpsrV = (result & 1) != 0;
}
}
ARMCoreArm.prototype.constructVMOV = function(condOp, to_arm_reg, n, t) {
var cpu : ARMCoreType = this.cpu;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
if (to_arm_reg) {
cpu.gprs[t] = cpu.ifprs[n];
} else {
cpu.ifprs[n] = cpu.gprs[t];
}
}
}
///////////////////////////////////////////////////////////////////////////
function ARMCoreThumb(cpu) {
@ -2658,6 +2928,9 @@ function ARMCore() {
this.generateConds();
this.gprs = new Int32Array(16);
this.dfprs = new Float64Array(16);
this.sfprs = new Float32Array(this.dfprs.buffer); // regs shared with dfprs
this.ifprs = new Int32Array(this.dfprs.buffer); // regs shared with dfprs
};
ARMCore.prototype.resetCPU = function(startOffset) {
@ -2665,6 +2938,7 @@ ARMCore.prototype.resetCPU = function(startOffset) {
this.gprs[i] = 0;
}
this.gprs[ARMRegs.PC] = startOffset + ARMConstants.WORD_SIZE_ARM;
this.dfprs.set(0); // no need to zero the sfprs, since they share the same buffer
this.loadInstruction = this.loadInstructionArm;
this.execMode = ARMMode.MODE_ARM;
@ -2769,6 +3043,9 @@ ARMCore.prototype.freeze = function() : ARMCoreState {
this.gprs[14],
this.gprs[15],
],
'sfprs': this.sfprs.slice(),
'dfprs': this.dfprs.slice(),
'ifprs': this.ifprs.slice(),
'mode': this.mode,
'cpsrI': this.cpsrI,
'cpsrF': this.cpsrF,
@ -2850,6 +3127,8 @@ ARMCore.prototype.defrost = function(frost: ARMCoreState) {
this.gprs[14] = frost.gprs[14];
this.gprs[15] = frost.gprs[15];
this.ifprs.set(frost.ifprs); // regs shared with sfprs
this.mode = frost.mode;
this.cpsrI = frost.cpsrI;
this.cpsrF = frost.cpsrF;
@ -3538,6 +3817,11 @@ ARMCore.prototype.compileArm = function(instruction) {
var load = instruction & 0x00100000;
var b = instruction & 0x00400000;
var i = instruction & 0x02000000;
// test for UDF instruction
if ((instruction & 0xfff000f0) == (0xe7f000f0|0)) {
var immediate = instruction & 0x0000000f; // TODO: full range
throw new EmuHalt("Program exited (" + immediate + ")");
}
var address : AddressFunction = function() {
throw new EmuHalt("Unimplemented memory access: 0x" + instruction.toString(16));
@ -3667,6 +3951,68 @@ ARMCore.prototype.compileArm = function(instruction) {
break;
case 0x0C000000:
// Coprocessor data transfer
var load = instruction & 0x00100000;
var w = instruction & 0x00200000;
var user = instruction & 0x00400000;
var u = instruction & 0x00800000;
var p = instruction & 0x01000000;
var rn = (instruction & 0x000F0000) >> 16;
var crd = (instruction & 0x0000F000) >> 12;
var cpnum = (instruction & 0x00000F00) >> 8;
var immediate = instruction & 0x000000FF;
var cond = (instruction >> 28) & 0xf;
var condOp = this.conds[cond];
// VPUSH, VPOP
if ((instruction & 0x0fbf0f00) == 0x0d2d0a00) {
op = this.armCompiler.constructVPUSH(condOp, (crd<<1)|(user?1:0), immediate, true);
}
else if ((instruction & 0x0fbf0f00) == 0x0d2d0b00) {
op = this.armCompiler.constructVPUSH(condOp, ((user?16:0)|crd)*2, immediate, false);
}
else if ((instruction & 0x0fbf0f00) == 0x0cbd0a00) {
op = this.armCompiler.constructVPOP(condOp, (crd<<1)|(user?1:0), immediate, true);
}
else if ((instruction & 0x0fbf0f00) == 0x0cbd0b00) {
op = this.armCompiler.constructVPOP(condOp, ((user?16:0)|crd)*2, immediate, false);
}
// VLDR, VSTR
// https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VSTR?lang=en
else if ((instruction & 0x0f200f00) == 0x0d000a00) {
immediate *= 4;
if (!u) immediate = -immediate;
var overlap = false;
var d = (crd<<1)|(user?1:0);
var address : AddressFunction;
if (w) {
address = this.armCompiler.constructAddressingMode4Writeback(immediate, offset, rn, overlap);
} else {
address = this.armCompiler.constructAddressingMode4(immediate, rn);
}
if (load) {
op = this.armCompiler.constructVLDR(condOp, d, address, true);
} else {
op = this.armCompiler.constructVSTR(condOp, d, address, true);
}
} else if ((instruction & 0x0f200f00) == 0x0d000b00) {
immediate *= 4;
if (!u) immediate = -immediate;
var overlap = false;
var d = ((user?16:0)|crd)*2;
var address : AddressFunction;
if (w) {
address = this.armCompiler.constructAddressingMode4Writeback(immediate, offset, rn, overlap);
} else {
address = this.armCompiler.constructAddressingMode4(immediate, rn);
}
if (load) {
op = this.armCompiler.constructVLDR(condOp, d, address, false);
} else {
op = this.armCompiler.constructVSTR(condOp, d, address, false);
}
}
break;
case 0x0E000000:
// Coprocessor data operation/SWI
@ -3676,6 +4022,148 @@ ARMCore.prototype.compileArm = function(instruction) {
op = this.armCompiler.constructSWI(immediate, condOp);
op.writesPC = false;
}
// VCVT, VCVTR, VCVT
// https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VCVT--VCVTR--between-floating-point-and-integer--Floating-point-
/*
if opc2 != '000' && !(opc2 IN "10x") then SEE "Related encodings";
to_integer = (opc2<2> == '1'); dp_operation = (sz == 1);
if to_integer then
unsigned = (opc2<0> == '0'); round_zero = (op == '1');
d = UInt(Vd:D); m = if dp_operation then UInt(M:Vm) else UInt(Vm:M);
else
unsigned = (op == '0'); round_nearest = FALSE; // FALSE selects FPSCR rounding
m = UInt(Vm:M); d = if dp_operation then UInt(D:Vd) else UInt(Vd:D);
*/
else if ((instruction & 0x0FB80E50) == 0x0EB80A40) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const opc2 = (instruction >> 16) & 0x7;
const Vd = (instruction >> 12) & 0xf;
const sz = (instruction >> 8) & 0x1;
const op0 = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
const to_integer = opc2 & 0x4;
const dp_operation = sz != 0;
const unsigned = to_integer ? opc2 & 0x1 : 0;
const round_zero = op0 != 0;
const round_nearest = false;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
//console.log("VCVT", d, m, opc2, to_integer, dp_operation, unsigned, round_zero, round_nearest);
op = this.armCompiler.constructVCVT(condOp, d, m, to_integer, dp_operation, unsigned, round_zero, round_nearest);
op.writesPC = false;
}
// VCVT f64/f32
else if ((instruction & 0x0FBF0ED0) == 0x0EB70AC0) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vd = (instruction >> 12) & 0xf;
const sz = (instruction >> 8) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
const double_to_single = sz != 0;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
op = this.armCompiler.constructVCVTF(condOp, d, m, double_to_single);
op.writesPC = false;
}
// 3-op floating point vector instructions (VADD, etc)
else if ((instruction & 0x0FA00E10) == 0x0E200A00) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const N = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const opcode = (instruction & 0x0F00000) >> 20;
const Vn = (instruction & 0x000F0000) >> 16;
const Vd = (instruction & 0x0000F000) >> 12;
const opcode2 = (instruction & 0b11100000) >> 5;
const Vm = instruction & 0x0000000F;
const sz = (instruction >> 8) & 0x1;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
const n = sz ? (N?16:0)|Vn : (Vn<<1)|(N?1:0);
var condOp = this.conds[cond];
op = this.armCompiler.constructVFP3Register(condOp, opcode, n, d, sz, opcode2, m);
op.writesPC = false;
}
// VDIV - https://developer.arm.com/documentation/ddi0597/2023-12/SIMD-FP-Instructions/VDIV--Divide-?lang=en
else if ((instruction & 0x0FB00C50) == 0x0E800800) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vn = (instruction >> 16) & 0xf;
const Vd = (instruction >> 12) & 0xf;
const size = (instruction >> 8) & 0x3;
const N = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
/*
case size of
when '01' esize = 16; d = UInt(Vd:D); n = UInt(Vn:N); m = UInt(Vm:M);
when '10' esize = 32; d = UInt(Vd:D); n = UInt(Vn:N); m = UInt(Vm:M);
when '11' esize = 64; d = UInt(D:Vd); n = UInt(N:Vn); m = UInt(M:Vm);
*/
const d = size==3 ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = size==3 ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
const n = size==3 ? (N?16:0)|Vn : (Vn<<1)|(N?1:0);
op = this.armCompiler.constructVFP3Register(condOp, 8, n, d, size==3, 0, m);
op.writesPC = false;
}
// 2-op floating point vector instructions (VCMP, etc)
else if ((instruction & 0x0FBF0E50) == 0x0EB40A40) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vd = (instruction >> 12) & 0xf;
const sz = (instruction >> 8) & 0x1;
const E = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0x0000000F;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
var condOp = this.conds[cond];
op = this.armCompiler.constructVCMP(condOp, d, Vd, sz, E, m, Vm);
op.writesPC = false;
}
// VCMP #0
else if ((instruction & 0x0FBF0EFF) == 0x0EB50A40) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vd = (instruction >> 12) & 0xf;
const sz = (instruction >> 8) & 0x1;
const E = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0x0000000F;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
var condOp = this.conds[cond];
op = this.armCompiler.constructVCMP0(condOp, d, Vd, sz, E, m, Vm);
op.writesPC = false;
}
// vmrs apsr_nzcv, fpscr (ignore, we always call this after CMP)
else if (instruction == 0xeef1fa10) {
op = this.armCompiler.constructNOP();
}
// VMOV - https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VMOV--between-ARM-core-register-and-single-precision-register-
else if ((instruction & 0x0FE00F10) == 0x0E000A10) {
const cond = (instruction >> 28) & 0xf;
const opc1 = (instruction >> 20) & 0x1;
const Vn = (instruction >> 16) & 0xf;
const Rt = (instruction >> 12) & 0xf;
const N = (instruction >> 7) & 0x1;
var condOp = this.conds[cond];
//console.log("VMOV", instruction.toString(16), opc1, Vn, Rt, N);
op = this.armCompiler.constructVMOV(condOp, opc1, (Vn<<1)|(N?1:0), Rt);
}
// vmov.32 dn[i], rn
else if (instruction == 0xee000b10) {
op = this.armCompiler.constructVMOV(condOp, false, 0, 0);
}
else if (instruction == 0xee201b10) {
op = this.armCompiler.constructVMOV(condOp, false, 1, 1);
}
break;
default:
throw new EmuHalt('Bad opcode: 0x' + instruction.toString(16));
@ -4103,11 +4591,16 @@ ARMCore.prototype.compileThumb = function(instruction) {
///////////////////////////////////////////////////////////////////////////
type ARMBus = Bus & Bus32;
export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQInterface, SavesState<ARMCoreState> {
core : ARMCoreType;
bus : Bus;
bus : ARMBus;
memory : ARMMemoryRegion[];
f64arr = new Float64Array(1);
f32arr = new Float32Array(this.f64arr.buffer);
i32arr = new Int32Array(this.f64arr.buffer);
BASE_OFFSET = 24;
OFFSET_MASK = 0x00FFFFFF;
@ -4122,8 +4615,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
this.memory = []; // TODO
for (var i=0; i<256; i++) {
// TODO: constant
var bits = 10;
var size = 0x80000;
const bits = 10;
this.memory[i] = {
PAGE_MASK: (2 << bits) - 1,
ICACHE_PAGE_BITS: bits,
@ -4146,12 +4638,13 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
isStable(): boolean {
return true; // TODO?
}
connectMemoryBus(bus: Bus): void {
connectMemoryBus(bus: ARMBus): void {
this.bus = bus;
}
reset(): void {
this.resetMemory();
this.core.resetCPU(0);
const resetVector = this.load32(0);
this.core.resetCPU(resetVector);
}
saveState() : ARMCoreState {
return this.core.freeze();
@ -4173,7 +4666,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
return this.bus.read(a) | (this.bus.read(a+1) << 8);
}
load32(a: number): number {
var v = this.bus.read(a) | (this.bus.read(a+1) << 8) | (this.bus.read(a+2) << 16) | (this.bus.read(a+3) << 24);
var v = this.bus.read32(a);
return v;
}
// TODO: memory.invalidatePage(maskedOffset);
@ -4185,10 +4678,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
this.bus.write(a+1, (v >> 8) & 0xff);
}
store32(a: number, v: number): void {
this.bus.write(a, v & 0xff);
this.bus.write(a+1, (v >> 8) & 0xff);
this.bus.write(a+2, (v >> 16) & 0xff);
this.bus.write(a+3, (v >> 24) & 0xff);
this.bus.write32(a, v);
}
// TODO
wait(a: number): void {
@ -4253,4 +4743,10 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
isThumb() : boolean {
return this.core.instructionWidth == 2;
}
getDebugTree() {
return {
state: this.saveState(),
mmu: this.core.mmu
};
}
}

View File

@ -1,115 +1,122 @@
export interface SavesState<S> {
saveState() : S;
loadState(state:S) : void;
saveState(): S;
loadState(state: S): void;
}
export interface Bus {
read(a:number) : number;
write(a:number, v:number) : void;
readConst?(a:number) : number;
read(a: number): number;
write(a: number, v: number): void;
readConst?(a: number): number;
}
export interface Bus32 {
read32(a: number): number;
write32(a: number, v: number): void;
readConst32?(a: number): number;
}
export interface ClockBased {
advanceClock() : void;
advanceClock(): void;
}
export interface InstructionBased {
advanceInsn() : number;
advanceInsn(): number;
}
export type TrapCondition = () => boolean;
export interface FrameBased {
advanceFrame(trap:TrapCondition) : number;
advanceFrame(trap: TrapCondition): number;
}
export interface VideoSource {
getVideoParams() : VideoParams;
connectVideo(pixels:Uint32Array) : void;
getVideoParams(): VideoParams;
connectVideo(pixels: Uint32Array): void;
}
export interface RasterFrameBased extends FrameBased, VideoSource {
getRasterY() : number;
getRasterX() : number;
getRasterY(): number;
getRasterX(): number;
getRasterCanvasPosition?(): { x: number, y: number };
}
export interface VideoParams {
width : number;
height : number;
overscan? : boolean;
rotate? : number;
videoFrequency? : number; // default = 60
aspect? : number;
width: number;
height: number;
overscan?: boolean;
rotate?: number;
videoFrequency?: number; // default = 60
aspect?: number;
}
// TODO: frame buffer optimization (apple2, etc)
export interface SampledAudioParams {
sampleRate : number;
stereo : boolean;
sampleRate: number;
stereo: boolean;
}
export interface SampledAudioSink {
feedSample(value:number, count:number) : void;
//sendAudioFrame(samples:Uint16Array) : void;
feedSample(value: number, count: number): void;
//sendAudioFrame(samples:Uint16Array) : void;
}
export interface SampledAudioSource {
getAudioParams() : SampledAudioParams;
connectAudio(audio : SampledAudioSink) : void;
getAudioParams(): SampledAudioParams;
connectAudio(audio: SampledAudioSink): void;
}
export interface AcceptsROM {
loadROM(data:Uint8Array, title?:string) : void;
loadROM(data: Uint8Array, title?: string): void;
}
export interface AcceptsBIOS {
loadBIOS(data:Uint8Array, title?:string) : void;
loadBIOS(data: Uint8Array, title?: string): void;
}
export interface Resettable {
reset() : void;
reset(): void;
}
export interface MemoryBusConnected {
connectMemoryBus(bus:Bus) : void;
connectMemoryBus(bus: Bus): void;
}
export interface IOBusConnected {
connectIOBus(bus:Bus) : void;
connectIOBus(bus: Bus): void;
}
export interface CPU extends MemoryBusConnected, Resettable, SavesState<any> {
getPC() : number;
getSP() : number;
isStable() : boolean;
getPC(): number;
getSP(): number;
isStable(): boolean;
}
export interface HasCPU extends Resettable {
cpu : CPU;
cpu: CPU;
}
export interface Interruptable<IT> {
interrupt(type:IT) : void;
interrupt(type: IT): void;
}
export interface SavesInputState<CS> {
loadControlsState(cs:CS) : void;
saveControlsState() : CS;
loadControlsState(cs: CS): void;
saveControlsState(): CS;
}
export interface AcceptsKeyInput {
setKeyInput(key:number, code:number, flags:number) : void;
setKeyInput(key: number, code: number, flags: number): void;
}
export interface AcceptsPaddleInput {
setPaddleInput(controller:number, value:number) : void;
setPaddleInput(controller: number, value: number): void;
}
// TODO: interface not yet used (setKeyInput() handles joystick)
export interface AcceptsJoyInput {
setJoyInput(joy:number, bitmask:number) : void;
setJoyInput(joy: number, bitmask: number): void;
}
// SERIAL I/O
@ -123,15 +130,15 @@ export interface SerialEvent {
// TODO: all these needed?
export interface SerialIOInterface {
// from machine to platform
clearToSend() : boolean;
sendByte(b : number);
clearToSend(): boolean;
sendByte(b: number);
// from platform to machine
byteAvailable() : boolean;
recvByte() : number;
byteAvailable(): boolean;
recvByte(): number;
// implement these too
reset() : void;
advance(clocks: number) : void;
// refresh() : void;
reset(): void;
advance(clocks: number): void;
// refresh() : void;
}
export interface HasSerialIO {
@ -143,62 +150,62 @@ export interface HasSerialIO {
/// PROFILER
export interface Probeable {
connectProbe(probe: ProbeAll) : void;
connectProbe(probe: ProbeAll): void;
}
export interface ProbeTime {
logClocks(clocks:number);
logClocks(clocks: number);
logNewScanline();
logNewFrame();
}
export interface ProbeCPU {
logExecute(address:number, SP:number);
logInterrupt(type:number);
logIllegal(address:number);
logWait(address:number);
logExecute(address: number, SP: number);
logInterrupt(type: number);
logIllegal(address: number);
logWait(address: number);
}
export interface ProbeBus {
logRead(address:number, value:number);
logWrite(address:number, value:number);
logDMARead(address:number, value:number);
logDMAWrite(address:number, value:number);
logRead(address: number, value: number);
logWrite(address: number, value: number);
logDMARead(address: number, value: number);
logDMAWrite(address: number, value: number);
}
export interface ProbeIO {
logIORead(address:number, value:number);
logIOWrite(address:number, value:number);
logIORead(address: number, value: number);
logIOWrite(address: number, value: number);
}
export interface ProbeVRAM {
logVRAMRead(address:number, value:number);
logVRAMWrite(address:number, value:number);
logVRAMRead(address: number, value: number);
logVRAMWrite(address: number, value: number);
}
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO, ProbeVRAM {
logData(data:number); // entire 32 bits
logData(data: number); // entire 32 bits
addLogBuffer(src: Uint32Array);
}
export class NullProbe implements ProbeAll {
logClocks() {}
logNewScanline() {}
logNewFrame() {}
logExecute() {}
logInterrupt() {}
logRead() {}
logWrite() {}
logIORead() {}
logIOWrite() {}
logVRAMRead() {}
logVRAMWrite() {}
logIllegal() {}
logWait() {}
logDMARead() {}
logDMAWrite() {}
logData() {}
addLogBuffer(src: Uint32Array) {}
logClocks() { }
logNewScanline() { }
logNewFrame() { }
logExecute() { }
logInterrupt() { }
logRead() { }
logWrite() { }
logIORead() { }
logIOWrite() { }
logVRAMRead() { }
logVRAMWrite() { }
logIllegal() { }
logWait() { }
logDMARead() { }
logDMAWrite() { }
logData() { }
addLogBuffer(src: Uint32Array) { }
}
/// CONVENIENCE
@ -215,33 +222,35 @@ export interface BasicMachineState extends BasicMachineControlsState {
export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, Probeable,
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
abstract cpuFrequency : number;
abstract defaultROMSize : number;
abstract cpuFrequency: number;
abstract defaultROMSize: number;
abstract cpu : CPU;
abstract ram : Uint8Array;
abstract cpu: CPU;
abstract ram: Uint8Array;
rom : Uint8Array;
inputs : Uint8Array = new Uint8Array(32);
handler : (key,code,flags) => void; // keyboard handler
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;
probe: ProbeAll = this.nullProbe;
setKeyInput(key:number, code:number, flags:number) : void {
this.handler && this.handler(key,code,flags);
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 {
connectProbe(probe: ProbeAll): void {
this.probe = probe || this.nullProbe;
}
reset() {
this.cpu.reset();
}
loadROM(data:Uint8Array, title?:string) : void {
loadROM(data: Uint8Array, title?: string): void {
if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize);
if (data.length > this.rom.length)
throw new Error(`ROM too big: ${data.length} > ${this.rom.length}}`);
this.rom.set(data);
}
loadState(state) {
@ -251,9 +260,9 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
}
saveState() {
return {
c:this.cpu.saveState(),
ram:this.ram.slice(0),
inputs:this.inputs.slice(0),
c: this.cpu.saveState(),
ram: this.ram.slice(0),
inputs: this.inputs.slice(0),
};
}
loadControlsState(state) {
@ -261,7 +270,7 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
}
saveControlsState() {
return {
inputs:this.inputs.slice(0)
inputs: this.inputs.slice(0)
};
}
advanceCPU() {
@ -273,102 +282,113 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
this.probe.logClocks(n);
return n;
}
probeMemoryBus(membus:Bus) : Bus {
probeMemoryBus(membus: Bus & Partial<Bus32>): Bus & Partial<Bus32> {
return {
read: (a) => {
let val = membus.read(a);
this.probe.logRead(a,val);
this.probe.logRead(a, val);
return val;
},
write: (a,v) => {
this.probe.logWrite(a,v);
membus.write(a,v);
write: (a, v) => {
this.probe.logWrite(a, v);
membus.write(a, v);
},
read32: (a) => {
let val = membus.read32(a);
this.probe.logRead(a, val);
return val;
},
write32: (a, v) => {
this.probe.logWrite(a, v);
membus.write32(a, v);
}
};
}
connectCPUMemoryBus(membus:Bus) : void {
this.cpu.connectMemoryBus(this.probeMemoryBus(membus));
connectCPUMemoryBus(membus: Bus): void {
this.cpu.connectMemoryBus(this.probeMemoryBus(membus as Bus&Bus32));
}
probeIOBus(iobus:Bus) : Bus {
probeIOBus(iobus: Bus): Bus {
return {
read: (a) => {
let val = iobus.read(a);
this.probe.logIORead(a,val);
this.probe.logIORead(a, val);
return val;
},
write: (a,v) => {
this.probe.logIOWrite(a,v);
iobus.write(a,v);
}
write: (a, v) => {
this.probe.logIOWrite(a, v);
iobus.write(a, v);
},
};
}
probeDMABus(iobus:Bus) : Bus {
probeDMABus(iobus: Bus): Bus {
return {
read: (a) => {
let val = iobus.read(a);
this.probe.logDMARead(a,val);
this.probe.logDMARead(a, val);
return val;
},
write: (a,v) => {
this.probe.logDMAWrite(a,v);
iobus.write(a,v);
write: (a, v) => {
this.probe.logDMAWrite(a, v);
iobus.write(a, v);
}
};
}
connectCPUIOBus(iobus:Bus) : void {
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;
aspectRatio : number;
pixels : Uint32Array;
audio : SampledAudioSink;
abstract canvasWidth: number;
abstract numVisibleScanlines: number;
abstract sampleRate: number;
overscan: boolean = false;
rotate: number = 0;
aspectRatio: number;
scanline : number;
getAudioParams() : SampledAudioParams {
return {sampleRate:this.sampleRate, stereo:false};
pixels: Uint32Array;
audio: SampledAudioSink;
scanline: number;
getAudioParams(): SampledAudioParams {
return { sampleRate: this.sampleRate, stereo: false };
}
connectAudio(audio : SampledAudioSink) : void {
connectAudio(audio: SampledAudioSink): void {
this.audio = audio;
}
getVideoParams() : VideoParams {
return {width:this.canvasWidth,
height:this.numVisibleScanlines,
aspect:this.aspectRatio,
overscan:this.overscan,
rotate:this.rotate};
getVideoParams(): VideoParams {
return {
width: this.canvasWidth,
height: this.numVisibleScanlines,
aspect: this.aspectRatio,
overscan: this.overscan,
rotate: this.rotate
};
}
connectVideo(pixels:Uint32Array) : void {
connectVideo(pixels: Uint32Array): void {
this.pixels = pixels;
}
}
export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased {
abstract numTotalScanlines : number;
abstract cpuCyclesPerLine : number;
abstract numTotalScanlines: number;
abstract cpuCyclesPerLine: number;
abstract startScanline() : void;
abstract drawScanline() : void;
abstract startScanline(): void;
abstract drawScanline(): void;
frameCycles : number;
advanceFrame(trap: TrapCondition) : number {
frameCycles: number;
advanceFrame(trap: TrapCondition): number {
this.preFrame();
var endLineClock = 0;
var steps = 0;
this.probe.logNewFrame();
this.frameCycles = 0;
for (var sl=0; sl<this.numTotalScanlines; sl++) {
for (var sl = 0; sl < this.numTotalScanlines; sl++) {
endLineClock += this.cpuCyclesPerLine; // could be fractional
this.scanline = sl;
this.startScanline();

View File

@ -215,6 +215,22 @@ export class VectorVideo extends RasterVideo {
}
}
export function drawCrosshair(ctx:CanvasRenderingContext2D, x:number, y:number, width:number) {
if (!ctx?.setLineDash) return; // for unit testing
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.fillRect(x-2, 0, 5, 32767);
ctx.fillRect(0, y-2, 32767, 5);
ctx.lineWidth = width;
ctx.strokeStyle = 'rgba(255,255,255,0.75)';
ctx.setLineDash([width*2,width*2]);
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 32767);
ctx.moveTo(0, y);
ctx.lineTo(32767, y);
ctx.stroke();
}
export class RAM {
mem : Uint8Array;
constructor(size:number) {
@ -642,7 +658,7 @@ export function padBytes(data:Uint8Array|number[], len:number, padstart?:boolean
type AddressReadWriteFn = ((a:number) => number) | ((a:number,v:number) => void);
type AddressDecoderEntry = [number, number, number, AddressReadWriteFn];
type AddressDecoderOptions = {gmask?:number};
type AddressDecoderOptions = {gmask?:number, defaultval?:number};
// TODO: better performance, check values
export function AddressDecoder(table : AddressDecoderEntry[], options?:AddressDecoderOptions) {
@ -663,7 +679,7 @@ export function AddressDecoder(table : AddressDecoderEntry[], options?:AddressDe
if (mask) s += "a&="+mask+";";
s += "return this.__fn"+i+"(a,v)&0xff;}\n";
}
s += "return 0;"; // TODO: noise()?
s += "return "+(options?.defaultval|0)+";";
return new Function('a', 'v', s);
}
return makeFunction().bind(self);

View File

@ -701,3 +701,15 @@ export function replaceAll(s:string, search:string, replace:string) : string {
if (search == '') return s;
return s.split(search).join(replace);
}
export function getCookie(name: string) : string {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}

View File

@ -0,0 +1,54 @@
import { WASIRunner } from "./wasishim";
export class LibRetroRunner extends WASIRunner {
constructor() {
super();
}
getEnv() {
return {
...super.getEnv(),
retro_environment_callback: (cmd: number, data: number) => {
console.log(`retro_environment_callback: ${cmd}, ${data}`);
return 0;
},
retro_video_refresh_callback: (data: number, width: number, height: number, pitch: number) => {
console.log(`retro_video_refresh_callback: ${data}, ${width}, ${height}, ${pitch}`);
},
retro_audio_sample_batch_callback: (data: number, frames: number) => {
console.log(`retro_audio_sample_batch_callback: ${data}, ${frames}`);
},
retro_audio_sample_callback: (left: number, right: number) => {
console.log(`retro_audio_sample_callback: ${left}, ${right}`);
return 0;
},
retro_input_poll_callback: () => {
console.log(`retro_input_poll_callback`);
},
retro_input_state_callback: (port: number, device: number, index: number, id: number) => {
console.log(`retro_input_state_callback: ${port}, ${device}, ${index}, ${id}`);
return 0;
},
}
}
retro_init() {
let errno = this.initialize();
// TODO: if (errno) throw new Error(`retro_init failed: ${errno}`);
this.exports().retro_init_callbacks();
this.exports().retro_init();
this.exports().retro_set_controller_port_device(0,1);
this.exports().retro_set_controller_port_device(1,1);
}
retro_api_version() {
return this.exports().retro_api_version();
}
load_rom(path: string, data: Uint8Array) {
const meta = '';
this.exports().retro_load_rom(path, data, data.length, meta);
}
reset() {
this.exports().retro_reset();
}
advance() {
this.exports().retro_run();
}
}

658
src/common/wasi/wasishim.ts Normal file
View File

@ -0,0 +1,658 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// https://dev.to/ndesmic/building-a-minimal-wasi-polyfill-for-browsers-4nel
// http://www.wasmtutor.com/webassembly-barebones-wasi
// https://github.com/emscripten-core/emscripten/blob/c017fc2d6961962ee87ae387462a099242dfbbd2/src/library_wasi.js#L451
// https://github.com/emscripten-core/emscripten/blob/c017fc2d6961962ee87ae387462a099242dfbbd2/src/library_fs.js
// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/sources/preopens.c
// https://fossies.org/linux/wasm3/source/extra/wasi_core.h
// https://wasix.org/docs/api-reference/wasi/fd_read
const use_debug = true;
const debug = use_debug ? console.log : () => { };
const warning = console.log;
export enum FDType {
UNKNOWN = 0,
BLOCK_DEVICE = 1,
CHARACTER_DEVICE = 2,
DIRECTORY = 3,
REGULAR_FILE = 4,
SOCKET_DGRAM = 5,
SOCKET_STREAM = 6,
SYMBOLIC_LINK = 7,
}
export enum FDRights {
FD_DATASYNC = 1,
FD_READ = 2,
FD_SEEK = 4,
FD_FDSTAT_SET_FLAGS = 8,
FD_SYNC = 16,
FD_TELL = 32,
FD_WRITE = 64,
FD_ADVISE = 128,
FD_ALLOCATE = 256,
PATH_CREATE_DIRECTORY = 512,
PATH_CREATE_FILE = 1024,
PATH_LINK_SOURCE = 2048,
PATH_LINK_TARGET = 4096,
PATH_OPEN = 8192,
FD_READDIR = 16384,
PATH_READLINK = 32768,
PATH_RENAME_SOURCE = 65536,
PATH_RENAME_TARGET = 131072,
PATH_FILESTAT_GET = 262144,
PATH_FILESTAT_SET_SIZE = 524288,
PATH_FILESTAT_SET_TIMES = 1048576,
FD_FILESTAT_GET = 2097152,
FD_FILESTAT_SET_SIZE = 4194304,
FD_FILESTAT_SET_TIMES = 8388608,
PATH_SYMLINK = 16777216,
PATH_REMOVE_DIRECTORY = 33554432,
PATH_UNLINK_FILE = 67108864,
POLL_FD_READWRITE = 134217728,
SOCK_SHUTDOWN = 268435456,
FD_ALL = 536870911, // TODO?
}
export enum FDFlags {
APPEND = 1,
DSYNC = 2,
NONBLOCK = 4,
RSYNC = 8,
SYNC = 16,
}
export enum FDOpenFlags {
CREAT = 1,
DIRECTORY = 2,
EXCL = 4,
TRUNC = 8,
}
export enum WASIErrors {
SUCCESS = 0,
TOOBIG = 1,
ACCES = 2,
ADDRINUSE = 3,
ADDRNOTAVAIL = 4,
AFNOSUPPORT = 5,
AGAIN = 6,
ALREADY = 7,
BADF = 8,
BADMSG = 9,
BUSY = 10,
CANCELED = 11,
CHILD = 12,
CONNABORTED = 13,
CONNREFUSED = 14,
CONNRESET = 15,
DEADLK = 16,
DESTADDRREQ = 17,
DOM = 18,
DQUOT = 19,
EXIST = 20,
FAULT = 21,
FBIG = 22,
HOSTUNREACH = 23,
IDRM = 24,
ILSEQ = 25,
INPROGRESS = 26,
INTR = 27,
INVAL = 28,
IO = 29,
ISCONN = 30,
ISDIR = 31,
LOOP = 32,
MFILE = 33,
MLINK = 34,
MSGSIZE = 35,
MULTIHOP = 36,
NAMETOOLONG = 37,
NETDOWN = 38,
NETRESET = 39,
NETUNREACH = 40,
NFILE = 41,
NOBUFS = 42,
NODEV = 43,
NOENT = 44,
NOEXEC = 45,
NOLCK = 46,
NOLINK = 47,
NOMEM = 48,
NOMSG = 49,
NOPROTOOPT = 50,
NOSPC = 51,
NOSYS = 52,
NOTCONN = 53,
NOTDIR = 54,
NOTEMPTY = 55,
NOTRECOVERABLE = 56,
NOTSOCK = 57,
NOTSUP = 58,
NOTTY = 59,
NXIO = 60,
OVERFLOW = 61,
OWNERDEAD = 62,
PERM = 63,
PIPE = 64,
PROTO = 65,
PROTONOSUPPORT = 66,
PROTOTYPE = 67,
RANGE = 68,
ROFS = 69,
SPIPE = 70,
SRCH = 71,
STALE = 72,
TIMEDOUT = 73,
TXTBSY = 74,
XDEV = 75,
NOTCAPABLE = 76,
}
export class WASIFileDescriptor {
fdindex: number = -1;
protected data: Uint8Array = new Uint8Array(16);
flags: number = 0;
size: number = 0;
offset: number = 0;
constructor(public name: string, public type: FDType, public rights: number) {
this.rights = -1; // TODO?
}
ensureCapacity(size: number) {
if (this.data.byteLength < size) {
const newdata = new Uint8Array(size * 2); // TODO?
newdata.set(this.data);
this.data = newdata;
}
}
write(chunk: Uint8Array) {
this.ensureCapacity(this.offset + chunk.byteLength);
this.data.set(chunk, this.offset);
this.offset += chunk.byteLength;
this.size = Math.max(this.size, this.offset);
}
read(chunk: Uint8Array) {
const len = Math.min(chunk.byteLength, this.size - this.offset);
chunk.set(this.data.subarray(this.offset, this.offset + len));
this.offset += len;
return len;
}
truncate() {
this.size = 0;
this.offset = 0;
}
llseek(offset: number, whence: number) {
switch (whence) {
case 0: // SEEK_SET
this.offset = offset;
break;
case 1: // SEEK_CUR
this.offset += offset;
break;
case 2: // SEEK_END
this.offset = this.size + offset;
break;
}
if (this.offset < 0) this.offset = 0;
if (this.offset > this.size) this.offset = this.size;
}
getBytes() {
return this.data.subarray(0, this.size);
}
getBytesAsString() {
return new TextDecoder().decode(this.getBytes());
}
toString() {
return `FD(${this.fdindex} "${this.name}" 0x${this.type.toString(16)} 0x${this.rights.toString(16)} ${this.offset}/${this.size}/${this.data.byteLength})`;
}
}
class WASIStreamingFileDescriptor extends WASIFileDescriptor {
constructor(fdindex: number, name: string, type: FDType, rights: number,
private stream: NodeJS.WritableStream) {
super(name, type, rights);
this.fdindex = fdindex;
}
write(chunk: Uint8Array) {
this.stream.write(chunk);
}
}
export interface WASIFilesystem {
getFile(name: string) : WASIFileDescriptor;
getFiles() : WASIFileDescriptor[];
getDirectories() : WASIFileDescriptor[];
}
export class WASIMemoryFilesystem implements WASIFilesystem {
private parent: WASIFilesystem | null = null;
private files: Map<string, WASIFileDescriptor> = new Map();
private dirs: Map<string, WASIFileDescriptor> = new Map();
constructor() {
this.putDirectory("/");
}
setParent(parent: WASIFilesystem) {
this.parent = parent;
}
putDirectory(name: string, rights?: number) {
if (!rights) rights = FDRights.PATH_OPEN | FDRights.PATH_CREATE_DIRECTORY | FDRights.PATH_CREATE_FILE;
if (name != '/' && name.endsWith('/')) name = name.substring(0, name.length - 1);
// add parent directory(s)
const parent = name.substring(0, name.lastIndexOf('/'));
if (parent && parent != name) {
this.putDirectory(parent, rights);
}
// add directory
const dir = new WASIFileDescriptor(name, FDType.DIRECTORY, rights);
this.dirs.set(name, dir);
return dir;
}
putFile(name: string, data: string | Uint8Array, rights?: number) {
if (typeof data === 'string') {
data = new TextEncoder().encode(data);
}
if (!rights) rights = FDRights.FD_READ | FDRights.FD_WRITE;
const file = new WASIFileDescriptor(name, FDType.REGULAR_FILE, rights);
file.write(data);
file.offset = 0;
this.files.set(name, file);
return file;
}
getFile(name: string) {
let file = this.files.get(name);
if (!file) {
file = this.parent?.getFile(name);
}
return file;
}
getDirectories() {
return [...this.dirs.values()];
}
getFiles() {
return [...this.files.values()];
}
}
export class WASIRunner {
#instance : any; // TODO
#memarr8: Uint8Array;
#memarr32: Int32Array;
#args: Uint8Array[] = [];
#envvars: Uint8Array[] = [];
stdin : WASIFileDescriptor;
stdout : WASIFileDescriptor;
stderr : WASIFileDescriptor;
fds: WASIFileDescriptor[] = [];
exited = false;
errno = -1;
fs = new WASIMemoryFilesystem();
constructor() {
this.createStdioBrowser();
}
exports() {
return this.#instance.exports;
}
createStdioNode() {
this.stdin = new WASIStreamingFileDescriptor(0, '<stdin>', FDType.CHARACTER_DEVICE, FDRights.FD_READ, process.stdin);
this.stdout = new WASIStreamingFileDescriptor(1, '<stdout>', FDType.CHARACTER_DEVICE, FDRights.FD_WRITE, process.stdout);
this.stderr = new WASIStreamingFileDescriptor(2, '<stderr>', FDType.CHARACTER_DEVICE, FDRights.FD_WRITE, process.stderr);
this.fds[0] = this.stdin;
this.fds[1] = this.stdout;
this.fds[2] = this.stderr;
}
createStdioBrowser() {
this.stdin = new WASIFileDescriptor('<stdin>', FDType.CHARACTER_DEVICE, FDRights.FD_READ);
this.stdout = new WASIFileDescriptor('<stdout>', FDType.CHARACTER_DEVICE, FDRights.FD_WRITE);
this.stderr = new WASIFileDescriptor('<stderr>', FDType.CHARACTER_DEVICE, FDRights.FD_WRITE);
this.stdin.fdindex = 0;
this.stdout.fdindex = 1;
this.stderr.fdindex = 2;
this.fds[0] = this.stdin;
this.fds[1] = this.stdout;
this.fds[2] = this.stderr;
}
initSync(wasmModule: WebAssembly.Module) {
this.#instance = new WebAssembly.Instance(wasmModule, this.getImportObject());
}
loadSync(wasmSource: Uint8Array) {
let wasmModule = new WebAssembly.Module(wasmSource);
this.initSync(wasmModule);
}
async loadAsync(wasmSource: Uint8Array) {
let wasmModule = await WebAssembly.compile(wasmSource);
this.#instance = await WebAssembly.instantiate(wasmModule, this.getImportObject());
}
setArgs(args: string[]) {
this.#args = args.map(arg => new TextEncoder().encode(arg + '\0'));
}
addPreopenDirectory(name: string) {
return this.openFile(name, FDOpenFlags.DIRECTORY | FDOpenFlags.CREAT);
}
openFile(path: string, o_flags: number, mode?: number): WASIFileDescriptor | number {
let file = this.fs.getFile(path);
mode = typeof mode == 'undefined' ? 438 /* 0666 */ : mode;
if (o_flags & FDOpenFlags.CREAT) {
if (file == null) {
if (o_flags & FDOpenFlags.DIRECTORY) {
file = this.fs.putDirectory(path);
} else {
file = this.fs.putFile(path, new Uint8Array(), FDRights.FD_ALL);
}
} else {
if (o_flags & FDOpenFlags.TRUNC) { // truncate
file.truncate();
} else return WASIErrors.INVAL;
}
} else {
if (file == null) return WASIErrors.NOSYS;
if (o_flags & FDOpenFlags.DIRECTORY) { // check type
if (file.type !== FDType.DIRECTORY) return WASIErrors.NOSYS;
}
if (o_flags & FDOpenFlags.EXCL) return WASIErrors.INVAL; // already exists
if (o_flags & FDOpenFlags.TRUNC) { // truncate
file.truncate();
} else {
file.llseek(0, 0); // seek to start
}
}
file.fdindex = this.fds.length;
this.fds.push(file);
return file;
}
mem8() {
if (!this.#memarr8?.byteLength) {
this.#memarr8 = new Uint8Array(this.#instance.exports.memory.buffer);
}
return this.#memarr8;
}
mem32() {
if (!this.#memarr32?.byteLength) {
this.#memarr32 = new Int32Array(this.#instance.exports.memory.buffer);
}
return this.#memarr32;
}
run() {
try {
this.#instance.exports._start();
if (!this.exited) {
this.exited = true;
this.errno = 0;
}
} catch (err) {
if (!this.exited) throw err;
}
return this.getErrno();
}
initialize() {
this.#instance.exports._initialize();
return this.getErrno();
}
getImportObject() {
return {
"wasi_snapshot_preview1": this.getWASISnapshotPreview1(),
"env": this.getEnv(),
}
}
peek8(ptr: number) {
return this.mem8()[ptr];
}
peek16(ptr: number) {
return this.mem8()[ptr] | (this.mem8()[ptr + 1] << 8);
}
peek32(ptr: number) {
return this.mem32()[ptr >>> 2];
}
poke8(ptr: number, val: number) {
this.mem8()[ptr] = val;
}
poke16(ptr: number, val: number) {
this.mem8()[ptr] = val;
this.mem8()[ptr + 1] = val >> 8;
}
poke32(ptr: number, val: number) {
this.mem32()[ptr >>> 2] = val;
}
poke64(ptr: number, val: number) {
this.mem32()[ptr >>> 2] = val;
this.mem32()[(ptr >>> 2) + 1] = 0;
}
pokeUTF8(str: string, ptr: number, maxlen: number) {
const enc = new TextEncoder();
const bytes = enc.encode(str);
const len = Math.min(bytes.length, maxlen);
this.mem8().set(bytes.subarray(0, len), ptr);
}
peekUTF8(ptr: number, maxlen: number) {
const bytes = this.mem8().subarray(ptr, ptr + maxlen);
const dec = new TextDecoder();
return dec.decode(bytes);
}
getErrno() {
return this.errno;
//let errno_ptr = this.#instance.exports.__errno_location();
//return this.peek32(errno_ptr);
}
poke_str_array_sizes(strs: Uint8Array[], count_ptr: number, buf_size_ptr: number) {
this.poke32(count_ptr, strs.length);
this.poke32(buf_size_ptr, strs.reduce((acc, arg) => acc + arg.length, 0));
}
poke_str_args(strs: Uint8Array[], argv_ptr: number, argv_buf_ptr: number) {
let argv = argv_ptr;
let argv_buf = argv_buf_ptr;
for (let arg of this.#args) {
this.poke32(argv, argv_buf);
argv += 4;
for (let i = 0; i < arg.length; i++) {
this.poke8(argv_buf, arg[i]);
argv_buf++;
}
}
}
args_sizes_get(argcount_ptr: number, argv_buf_size_ptr: number) {
debug("args_sizes_get", argcount_ptr, argv_buf_size_ptr);
this.poke_str_array_sizes(this.#args, argcount_ptr, argv_buf_size_ptr);
return 0;
}
args_get(argv_ptr: number, argv_buf_ptr: number) {
debug("args_get", argv_ptr, argv_buf_ptr);
this.poke_str_args(this.#args, argv_ptr, argv_buf_ptr);
return 0;
}
environ_sizes_get(environ_count_ptr: number, environ_buf_size_ptr: number) {
debug("environ_sizes_get", environ_count_ptr, environ_buf_size_ptr);
this.poke_str_array_sizes(this.#envvars, environ_count_ptr, environ_buf_size_ptr);
return 0;
}
environ_get(environ_ptr: number, environ_buf_ptr: number) {
debug("environ_get", environ_ptr, environ_buf_ptr);
this.poke_str_args(this.#envvars, environ_ptr, environ_buf_ptr);
return 0;
}
fd_write(fd, iovs, iovs_len, nwritten_ptr) {
const stream = this.fds[fd];
const iovecs = this.mem32().subarray(iovs >>> 2, (iovs + iovs_len * 8) >>> 2);
let total = 0;
for (let i = 0; i < iovs_len; i++) {
const ptr = iovecs[i * 2];
const len = iovecs[i * 2 + 1];
const chunk = this.mem8().subarray(ptr, ptr + len);
total += len;
stream.write(chunk);
}
this.poke32(nwritten_ptr, total);
debug("fd_write", fd, iovs, iovs_len, '->', total);
return 0;
}
fd_read(fd, iovs, iovs_len, nread_ptr) {
const stream = this.fds[fd];
const iovecs = this.mem32().subarray(iovs >>> 2, (iovs + iovs_len * 8) >>> 2);
let total = 0;
for (let i = 0; i < iovs_len; i++) {
const ptr = iovecs[i * 2];
const len = iovecs[i * 2 + 1];
const chunk = this.mem8().subarray(ptr, ptr + len);
total += stream.read(chunk);
}
this.poke32(nread_ptr, total);
debug("fd_read", fd, iovs, iovs_len, '->', total);
return WASIErrors.SUCCESS;
}
fd_seek(fd: number, offset: number, whence: number, newoffset_ptr: number) {
const file = this.fds[fd];
if (typeof offset == 'bigint') offset = Number(offset);
debug("fd_seek", fd, offset, whence, file+"");
if (file != null) {
file.llseek(offset, whence);
this.poke64(newoffset_ptr, file.offset);
return WASIErrors.SUCCESS;
}
return WASIErrors.BADF;
}
fd_close(fd: number) {
debug("fd_close", fd);
const file = this.fds[fd];
if (file != null) {
this.fds[fd] = null;
return 0;
}
return WASIErrors.BADF;
}
proc_exit(errno: number) {
debug("proc_exit", errno);
this.errno = errno;
this.exited = true;
}
fd_prestat_get(fd: number, prestat_ptr: number) {
const file = this.fds[fd];
debug("fd_prestat_get", fd, prestat_ptr, file?.name);
if (file && file.type === FDType.DIRECTORY) {
const enc_name = new TextEncoder().encode(file.name);
this.poke8(prestat_ptr + 0, 0); // __WASI_PREOPENTYPE_DIR
this.poke64(prestat_ptr + 8, enc_name.length);
return WASIErrors.SUCCESS;
}
return WASIErrors.BADF;
}
fd_fdstat_get(fd: number, fdstat_ptr: number) {
const file = this.fds[fd];
debug("fd_fdstat_get", fd, fdstat_ptr, file + "");
if (file != null) {
this.poke16(fdstat_ptr + 0, file.type); // fs_filetype
this.poke16(fdstat_ptr + 2, file.flags); // fs_flags
this.poke64(fdstat_ptr + 8, file.rights); // fs_rights_base
this.poke64(fdstat_ptr + 16, file.rights); // fs_rights_inheriting
return WASIErrors.SUCCESS;
}
return WASIErrors.BADF;
}
fd_prestat_dir_name(fd: number, path_ptr: number, path_len: number) {
const file = this.fds[fd];
debug("fd_prestat_dir_name", fd, path_ptr, path_len);
if (file != null) {
this.pokeUTF8(file.name, path_ptr, path_len);
return WASIErrors.SUCCESS;
}
return WASIErrors.INVAL;
}
path_open(dirfd: number, dirflags: number, path_ptr: number, path_len: number,
o_flags: number, fs_rights_base: number, fs_rights_inheriting: number,
fd_flags: number, fd_ptr: number)
{
const dir = this.fds[dirfd];
if (dir == null) return WASIErrors.BADF;
if (dir.type !== FDType.DIRECTORY) return WASIErrors.NOTDIR;
const filename = this.peekUTF8(path_ptr, path_len);
const path = dir.name + '/' + filename;
const fd = this.openFile(path, o_flags, fd_flags);
debug("path_open", path, dirfd, dirflags,
o_flags, //fs_rights_base, fs_rights_inheriting,
fd_flags, fd_ptr, '->', fd + "");
if (typeof fd === 'number') return fd; // error msg
this.poke32(fd_ptr, fd.fdindex);
return WASIErrors.SUCCESS;
}
random_get(ptr: number, len: number) {
debug("random_get", ptr, len);
for (let i=0; i<len; i++) {
// TODO: don't use for crypto
this.poke8(ptr + i, Math.floor(Math.random() * 256));
}
return WASIErrors.SUCCESS;
}
path_filestat_get(dirfd: number, dirflags: number, path_ptr: number, path_len: number, filestat_ptr: number) {
const dir = this.fds[dirfd];
if (dir == null) return WASIErrors.BADF;
if (dir.type !== FDType.DIRECTORY) return WASIErrors.NOTDIR;
const filename = this.peekUTF8(path_ptr, path_len);
const path = dir.name + '/' + filename;
const fd = this.fs.getFile(path);
console.log("path_filestat_get", dir+"", path, filestat_ptr, '->', fd+"");
if (!fd) return WASIErrors.NOENT;
this.poke64(filestat_ptr, fd.fdindex); // dev
this.poke64(filestat_ptr + 8, 0); // ino
this.poke8(filestat_ptr + 16, fd.type); // filetype
this.poke64(filestat_ptr + 24, 1); // nlink
this.poke64(filestat_ptr + 32, fd.size); // size
this.poke64(filestat_ptr + 40, 0); // atim
this.poke64(filestat_ptr + 48, 0); // mtim
this.poke64(filestat_ptr + 56, 0); // ctim
}
getWASISnapshotPreview1() {
return {
args_sizes_get: this.args_sizes_get.bind(this),
args_get: this.args_get.bind(this),
environ_sizes_get: this.environ_sizes_get.bind(this),
environ_get: this.environ_get.bind(this),
proc_exit: this.proc_exit.bind(this),
path_open: this.path_open.bind(this),
fd_prestat_get: this.fd_prestat_get.bind(this),
fd_prestat_dir_name: this.fd_prestat_dir_name.bind(this),
fd_fdstat_get: this.fd_fdstat_get.bind(this),
fd_read: this.fd_read.bind(this),
fd_write: this.fd_write.bind(this),
fd_seek: this.fd_seek.bind(this),
fd_close: this.fd_close.bind(this),
path_filestat_get: this.path_filestat_get.bind(this),
random_get: this.random_get.bind(this),
fd_fdstat_set_flags() { warning("TODO: fd_fdstat_set_flags"); return WASIErrors.NOTSUP; },
fd_readdir() { warning("TODO: fd_readdir"); return WASIErrors.NOTSUP; },
path_unlink_file() { warning("TODO: path_unlink_file"); return WASIErrors.NOTSUP; },
clock_time_get() { warning("TODO: clock_time_get"); return WASIErrors.NOTSUP; },
fd_tell() { warning("TODO: fd_tell"); return WASIErrors.NOTSUP; },
}
}
getEnv() {
return {
__syscall_unlinkat() { warning('TODO: unlink'); return WASIErrors.NOTSUP; },
}
}
}

View File

@ -66,13 +66,16 @@ export abstract class BaseWASMMachine {
this.exports = wasmResult.exports;
} else throw new Error('could not load WASM file');
}
allocateBIOS(biosBinary: Uint8Array) {
this.biosptr = this.exports.malloc(biosBinary.byteLength);
this.biosarr = new Uint8Array(this.exports.memory.buffer, this.biosptr, biosBinary.byteLength);
}
async fetchBIOS() {
var biosResponse = await fetch('res/'+this.prefix+'.bios');
if (biosResponse.status == 200 || (biosResponse as any as Blob).size) {
var biosBinary = await biosResponse.arrayBuffer();
this.biosptr = this.exports.malloc(biosBinary.byteLength);
this.biosarr = new Uint8Array(this.exports.memory.buffer, this.biosptr, biosBinary.byteLength);
this.loadBIOS(new Uint8Array(biosBinary));
var biosBinary = new Uint8Array(await biosResponse.arrayBuffer());
this.allocateBIOS(biosBinary);
this.loadBIOS(biosBinary);
} else throw new Error('could not load BIOS file');
}
async initWASM() {

View File

@ -120,7 +120,14 @@ export type CodeListingMap = {[path:string]:CodeListing};
export type VerilogOutput =
{program_rom_variable:string, program_rom:Uint8Array, code:string, name:string, ports:any[], signals:any[]};
export type Segment = {name:string, start:number, size:number, last?:number, type?:string};
export type Segment = {
name:string,
start:number,
size:number,
last?:number,
type?:string,
source?:'native'|'linker'
};
export type WorkerResult = WorkerErrorResult | WorkerOutputResult<any> | WorkerUnchangedResult;

14
src/ide/analytics.ts Normal file
View File

@ -0,0 +1,14 @@
declare var ga;
export function gaEvent(category: string, action: string, label?: string, value?: string) {
if (window['ga']) {
ga('send', 'event', category, action, label, value);
}
}
export function gaPageView(page: string) {
if (window['ga']) {
ga('send', 'pageview', page);
}
}

34
src/ide/dialogs.ts Normal file
View File

@ -0,0 +1,34 @@
import DOMPurify from "dompurify";
export function setWaitDialog(b: boolean) {
if (b) {
setWaitProgress(0);
$("#pleaseWaitModal").modal('show');
} else {
setWaitProgress(1);
$("#pleaseWaitModal").modal('hide');
}
}
export function setWaitProgress(prog: number) {
$("#pleaseWaitProgressBar").css('width', (prog * 100) + '%').show();
}
export function alertError(s: string) {
setWaitDialog(false);
bootbox.alert({
title: '<span class="glyphicon glyphicon-alert" aria-hidden="true"></span> Alert',
message: DOMPurify.sanitize(s)
});
}
export function alertInfo(s: string) {
setWaitDialog(false);
bootbox.alert(DOMPurify.sanitize(s));
}
export function fatalError(s: string) {
alertError(s);
throw new Error(s);
}

View File

@ -189,12 +189,12 @@ export class CodeProject {
} else {
// for .asm -- [.%]include "file"
// for .c -- #include "file"
let re2 = /^\s*[.#%]?(include|incbin)\s+"(.+?)"/gmi;
let re2 = /^\s*[.#%]?(include|incbin|embed)\s+"(.+?)"/gmi;
while (m = re2.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for .c -- //#resource "file" (or ;resource or #resource)
let re3 = /^\s*([;']|[/][/])#(resource|incbin)\s+"(.+?)"/gm;
let re3 = /^\s*([;']|[/][/])#(resource)\s+"(.+?)"/gm;
while (m = re3.exec(text)) {
this.pushAllFiles(files, m[3]);
}
@ -217,6 +217,11 @@ export class CodeProject {
while (m = re6.exec(text)) {
this.pushAllFiles(files, m[2]);
}
// for acme
let re7 = /^[!]src\s+"(.+?)"/gmi;
while (m = re7.exec(text)) {
this.pushAllFiles(files, m[1]);
}
}
return files;
}
@ -269,7 +274,8 @@ export class CodeProject {
var depfiles = [];
msg.updates.push({path:mainfilename, data:maintext});
this.filename2path[mainfilename] = this.mainPath;
let usesRemoteTool = this.getToolForFilename(mainfilename).startsWith('remote:');
const tool = this.getToolForFilename(this.mainPath);
let usesRemoteTool = tool.startsWith('remote:');
for (var dep of depends) {
// remote tools send both includes and linked files in one build step
if (!dep.link || usesRemoteTool) {
@ -404,8 +410,12 @@ export class CodeProject {
processBuildSegments(data: WorkerOutputResult<any>) {
// save and sort segment list
var segs = (this.platform.getMemoryMap && this.platform.getMemoryMap()["main"]) || [];
if (data.segments) { segs = segs.concat(data.segments || []); }
var segs : Segment[] = (this.platform.getMemoryMap && this.platform.getMemoryMap()["main"]) || [];
if (segs?.length) { segs.forEach(seg => seg.source = 'native'); }
if (data.segments) {
data.segments.forEach(seg => seg.source = 'linker');
segs = segs.concat(data.segments || []);
}
segs.sort((a,b) => {return a.start-b.start});
this.segments = segs;
}

290
src/ide/shareexport.ts Normal file
View File

@ -0,0 +1,290 @@
import { OutputSoundFile, TAPFile } from '../common/audio/CommodoreTape';
import { byteArrayToString, compressLZG, getBasePlatform, getFilenameForPath, getFilenamePrefix, loadScript } from '../common/util';
import { alertError, alertInfo, setWaitDialog, setWaitProgress } from './dialogs';
import { getCurrentEditorFilename, getCurrentMainFilename, getCurrentOutput, getCurrentProject, getPlatformStore, getWorkerParams, platform, platform_id, projectWindows } from './ui';
import { saveAs } from "file-saver";
declare var GIF;
export function _shareEmbedLink(e) {
if (getCurrentOutput() == null) {
alertError("Please fix errors before sharing.");
return true;
}
if (!(getCurrentOutput() instanceof Uint8Array)) {
alertError("Can't share a Verilog executable yet. (It's not actually a ROM...)");
return true;
}
loadClipboardLibrary();
loadScript('lib/liblzg.js').then(() => {
// TODO: Module is bad var name (conflicts with MAME)
var lzgrom = compressLZG(window['Module'], Array.from(<Uint8Array>getCurrentOutput()));
window['Module'] = null; // so we load it again next time
var lzgb64 = btoa(byteArrayToString(lzgrom));
var embed = {
p: platform_id,
//n: current_project.mainPath,
r: lzgb64
};
var linkqs = $.param(embed);
var fulllink = get8bitworkshopLink(linkqs, 'player.html');
var iframelink = '<iframe width=640 height=600 src="' + fulllink + '">';
$("#embedLinkTextarea").text(fulllink);
$("#embedIframeTextarea").text(iframelink);
$("#embedLinkModal").modal('show');
$("#embedAdviceWarnAll").hide();
$("#embedAdviceWarnIE").hide();
if (fulllink.length >= 65536) $("#embedAdviceWarnAll").show();
else if (fulllink.length >= 5120) $("#embedAdviceWarnIE").show();
});
return true;
}
function loadClipboardLibrary() {
// can happen in background because it won't be used until user clicks
console.log('clipboard');
import('clipboard').then((clipmod) => {
let ClipboardJS = clipmod.default;
new ClipboardJS(".btn");
});
}
function get8bitworkshopLink(linkqs: string, fn: string) {
console.log(linkqs);
var loc = window.location;
var prefix = loc.pathname.replace('index.html', '');
var protocol = (loc.host == '8bitworkshop.com') ? 'https:' : loc.protocol;
var fulllink = protocol + '//' + loc.host + prefix + fn + '?' + linkqs;
return fulllink;
}
function _downloadCassetteFile_apple2(e) {
var addr = getWorkerParams()?.code_start;
loadScript('lib/c2t.js').then(() => {
var stdout = '';
var print_fn = function (s) { stdout += s + "\n"; }
var c2t = window['c2t']({
noInitialRun: true,
print: print_fn,
printErr: print_fn
});
var FS = c2t['FS'];
var rompath = getCurrentMainFilename() + ".bin";
var audpath = getCurrentMainFilename() + ".wav";
FS.writeFile(rompath, getCurrentOutput(), { encoding: 'binary' });
var args = ["-2bc", rompath + ',' + addr.toString(16), audpath];
c2t.callMain(args);
var audout = FS.readFile(audpath, { 'encoding': 'binary' });
if (audout) {
var blob = new Blob([audout], { type: "audio/wav" });
saveAs(blob, audpath);
stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file.";
alertInfo(stdout);
}
});
}
export function _downloadCassetteFile_vcs(e) {
loadScript('lib/makewav.js').then(() => {
let stdout = '';
let print_fn = function (s) { stdout += s + "\n"; }
var prefix = getFilenamePrefix(getCurrentMainFilename());
let rompath = prefix + ".bin";
let audpath = prefix + ".wav";
let _makewav = window['makewav']({
noInitialRun: false,
print: print_fn,
printErr: print_fn,
arguments: ['-ts', '-f0', '-v10', rompath],
preRun: (mod) => {
let FS = mod['FS'];
FS.writeFile(rompath, getCurrentOutput(), { encoding: 'binary' });
}
});
_makewav.ready.then((makewav) => {
let args = [rompath];
makewav.run(args);
console.log(stdout);
let FS = makewav['FS'];
let audout = FS.readFile(audpath, { 'encoding': 'binary' });
if (audout) {
let blob = new Blob([audout], { type: "audio/wav" });
saveAs(blob, audpath);
stdout += "\nConnect your audio output to the SuperCharger input, turn up the volume, and play the audio file.";
alertInfo(stdout);
}
});
});
}
function _downloadCassetteFile_c64(e) {
var prefix = getFilenamePrefix(getCurrentMainFilename());
let audpath = prefix + ".tap";
let tapmaker = new TAPFile(prefix);
let outfile = new OutputSoundFile({ sine_wave: true });
let data = getCurrentOutput();
let startAddress = data[0] + data[1] * 256;
data = data.slice(2); // remove header
tapmaker.setContent({ data, startAddress, type: TAPFile.FILE_TYPE_NON_RELOCATABLE });
tapmaker.generateSound(outfile);
let tapout = outfile.getTAPData();
//let audout = outfile.getSoundData();
if (tapout) {
//let blob = new Blob([audout], { type: "audio/wav" });
let blob = new Blob([tapout], { type: "application/octet-stream" });
saveAs(blob, audpath);
}
}
export function _getCassetteFunction() {
switch (getBasePlatform(platform_id)) {
case 'vcs': return _downloadCassetteFile_vcs;
case 'apple2': return _downloadCassetteFile_apple2;
case 'c64': return _downloadCassetteFile_c64;
}
}
export function _downloadCassetteFile(e) {
if (getCurrentOutput() == null) {
alertError("Please fix errors before exporting.");
return true;
}
var fn = _getCassetteFunction();
if (fn === undefined) {
alertError("Cassette export is not supported on this platform.");
return true;
}
fn(e);
}
export function _downloadROMImage(e) {
if (getCurrentOutput() == null) {
alertError("Please finish compiling with no errors before downloading ROM.");
return true;
}
var prefix = getFilenamePrefix(getCurrentMainFilename());
if (platform.getDownloadFile) {
var dl = platform.getDownloadFile();
var prefix = getFilenamePrefix(getCurrentMainFilename());
saveAs(dl.blob, prefix + dl.extension);
} else if (getCurrentOutput() instanceof Uint8Array) {
var blob = new Blob([getCurrentOutput()], { type: "application/octet-stream" });
var suffix = (platform.getROMExtension && platform.getROMExtension(getCurrentOutput()))
|| "-" + getBasePlatform(platform_id) + ".bin";
saveAs(blob, prefix + suffix);
} else {
alertError(`The "${platform_id}" platform doesn't have downloadable ROMs.`);
}
}
export function _downloadSourceFile(e) {
var text = projectWindows.getCurrentText();
if (!text) return false;
var blob = new Blob([text], { type: "text/plain;charset=utf-8" });
saveAs(blob, getCurrentEditorFilename(), { autoBom: false });
}
async function newJSZip() {
let JSZip = (await import('jszip')).default;
return new JSZip();
}
export async function _downloadProjectZipFile(e) {
var zip = await newJSZip();
getCurrentProject().iterateFiles((id, data) => {
if (data) {
zip.file(getFilenameForPath(id), data);
}
});
zip.generateAsync({ type: "blob" }).then((content) => {
saveAs(content, getCurrentMainFilename() + "-" + getBasePlatform(platform_id) + ".zip");
});
}
export function _downloadSymFile(e) {
let symfile = platform.getDebugSymbolFile && platform.getDebugSymbolFile();
if (!symfile) {
alertError("This project does not have debug information.");
return;
}
var prefix = getFilenamePrefix(getCurrentMainFilename());
saveAs(symfile.blob, prefix + symfile.extension, { autoBom: false });
}
export async function _downloadAllFilesZipFile(e) {
var zip = await newJSZip();
var keys = await getPlatformStore().keys();
setWaitDialog(true);
try {
var i = 0;
await Promise.all(keys.map((path) => {
return getPlatformStore().getItem(path).then((text) => {
setWaitProgress(i++ / (keys.length + 1));
if (text) {
zip.file(path, text as any);
}
});
}));
var content = await zip.generateAsync({ type: "blob" });
saveAs(content, getBasePlatform(platform_id) + "-all.zip");
} finally {
setWaitDialog(false);
}
}
var recordingVideo = false;
export function _recordVideo() {
if (recordingVideo) return;
loadScript("lib/gif.js").then(() => {
var canvas = $("#emulator").find("canvas")[0] as HTMLElement;
if (!canvas) {
alertError("Could not find canvas element to record video!");
return;
}
var rotate = 0;
if (canvas.style && canvas.style.transform) {
if (canvas.style.transform.indexOf("rotate(-90deg)") >= 0)
rotate = -1;
else if (canvas.style.transform.indexOf("rotate(90deg)") >= 0)
rotate = 1;
}
var gif = new GIF({
workerScript: 'lib/gif.worker.js',
workers: 4,
quality: 10,
rotate: rotate
});
var img = $('#videoPreviewImage');
gif.on('progress', (prog) => {
setWaitProgress(prog);
});
gif.on('finished', (blob) => {
img.attr('src', URL.createObjectURL(blob));
setWaitDialog(false);
platform.resume();
$("#videoPreviewModal").modal('show');
});
var intervalMsec = 20;
var maxFrames = 300;
var nframes = 0;
console.log("Recording video", canvas);
$("#emulator").css('backgroundColor', '#cc3333');
var f = () => {
if (nframes++ > maxFrames) {
console.log("Rendering video");
$("#emulator").css('backgroundColor', 'inherit');
setWaitDialog(true);
platform.pause();
gif.render();
recordingVideo = false;
} else {
gif.addFrame(canvas, { delay: intervalMsec, copy: true });
setTimeout(f, intervalMsec);
recordingVideo = true;
}
};
f();
});
}

268
src/ide/sync.ts Normal file
View File

@ -0,0 +1,268 @@
import DOMPurify from "dompurify";
import { getCookie, getFilenameForPath, getFilenamePrefix, loadScript } from "../common/util";
import { gaEvent } from "./analytics";
import { alertError, alertInfo, setWaitDialog, setWaitProgress } from "./dialogs";
import { createNewPersistentStore } from "./project";
import { GHSession, GithubService, getRepos, parseGithubURL } from "./services";
import { getCurrentMainFilename, getCurrentOutput, getCurrentProject, getPlatformStore, gotoNewLocation, projectWindows, repo_id } from "./ui";
declare var Octokat;
var githubService: GithubService;
export async function getGithubService() {
if (!githubService) {
// load github API client
await loadScript('lib/octokat.js');
// load firebase
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js');
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js');
await loadScript('https://8bitworkshop.com/config.js');
// get github API key from cookie
// TODO: move to service?
var ghkey = getCookie('__github_key');
githubService = new GithubService(Octokat, ghkey, getPlatformStore(), getCurrentProject());
console.log("loaded github service");
}
return githubService;
}
export function getBoundGithubURL(): string {
var toks = (repo_id || '').split('/');
if (toks.length != 2) {
alertError("You are not in a GitHub repository. Choose one from the pulldown, or Import or Publish one.");
return null;
}
return 'https://github.com/' + toks[0] + '/' + toks[1];
}
// GITHUB stuff (TODO: move)
export async function importProjectFromGithub(githuburl: string, replaceURL: boolean) {
var sess: GHSession;
var urlparse = parseGithubURL(githuburl);
if (!urlparse) {
alertError('Could not parse Github URL.');
return;
}
// redirect to repo if exists
var existing = getRepos()[urlparse.repopath];
if (existing && !confirm("You've already imported " + urlparse.repopath + " -- do you want to replace all local files?")) {
return;
}
// create new store for imported repository
setWaitDialog(true);
var newstore = createNewPersistentStore(urlparse.repopath);
// import into new store
setWaitProgress(0.25);
var gh = await getGithubService();
return gh.import(githuburl).then((sess1: GHSession) => {
sess = sess1;
setWaitProgress(0.75);
return gh.pull(githuburl, newstore);
}).then((sess2: GHSession) => {
// TODO: only first session has mainPath?
// reload repo
setWaitDialog(false);
gaEvent('sync', 'import', githuburl);
gotoNewLocation(replaceURL, { repo: urlparse.repopath }); // file:sess.mainPath, platform:sess.platform_id};
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not import " + githuburl + "." + e);
});
}
export async function _loginToGithub(e) {
var gh = await getGithubService();
gh.login().then(() => {
alertInfo("You are signed in to Github.");
}).catch((e) => {
alertError("Could not sign in." + e);
});
}
export async function _logoutOfGithub(e) {
var gh = await getGithubService();
gh.logout().then(() => {
alertInfo("You are logged out of Github.");
});
}
export function _importProjectFromGithub(e) {
var modal = $("#importGithubModal");
var btn = $("#importGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var githuburl = $("#importGithubURL").val() + "";
modal.modal('hide');
importProjectFromGithub(githuburl, false);
});
}
export function _publishProjectToGithub(e) {
if (repo_id) {
if (!confirm("This project (" + getCurrentProject().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");
$("#githubRepoName").val(getFilenamePrefix(getFilenameForPath(getCurrentProject().mainPath)));
modal.modal('show');
btn.off('click').on('click', async () => {
var name = $("#githubRepoName").val() + "";
var desc = $("#githubRepoDesc").val() + "";
var priv = $("#githubRepoPrivate").val() == 'private';
var license = $("#githubRepoLicense").val() + "";
var sess;
if (!name) {
alertError("You did not enter a project name.");
return;
}
modal.modal('hide');
setWaitDialog(true);
var gh = await getGithubService();
gh.login().then(() => {
setWaitProgress(0.25);
return gh.publish(name, desc, license, priv);
}).then((_sess) => {
sess = _sess;
setWaitProgress(0.5);
//repo_id = qs.repo = sess.repopath;
return pushChangesToGithub('initial import from 8bitworkshop.com');
}).then(() => {
gaEvent('sync', 'publish', priv ? "" : name);
importProjectFromGithub(sess.url, false);
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not publish GitHub repository: " + e);
});
});
}
export function _pushProjectToGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
var modal = $("#pushGithubModal");
var btn = $("#pushGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var commitMsg = $("#githubCommitMsg").val() + "";
modal.modal('hide');
pushChangesToGithub(commitMsg);
});
}
export function _pullProjectFromGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.confirm("Pull from repository and replace all local files? Any changes you've made will be overwritten.",
async (ok) => {
if (ok) {
setWaitDialog(true);
var gh = await getGithubService();
gh.pull(ghurl).then((sess: GHSession) => {
setWaitDialog(false);
projectWindows.updateAllOpenWindows(getPlatformStore());
});
}
});
}
function confirmCommit(sess): Promise<GHSession> {
return new Promise((resolve, reject) => {
var files = sess.commit.files;
console.log(files);
// anything changed?
if (files.length == 0) {
setWaitDialog(false);
alertInfo("No files changed.");
return;
}
// build commit confirm message
var msg = "";
for (var f of files) {
msg += DOMPurify.sanitize(f.filename) + ": " + f.status;
if (f.additions || f.deletions || f.changes) {
msg += " (" + f.additions + " additions, " + f.deletions + " deletions, " + f.changes + " changes)";
};
msg += "<br/>";
}
// show dialog, continue when yes
bootbox.confirm(msg, (ok) => {
if (ok) {
resolve(sess);
} else {
setWaitDialog(false);
}
});
});
}
async function pushChangesToGithub(message: string) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
// build file list for push
var files = [];
for (var path in getCurrentProject().filedata) {
var newpath = getCurrentProject().stripLocalPath(path);
var data = getCurrentProject().filedata[path];
if (newpath && data) {
files.push({ path: newpath, data: data });
}
}
// include built ROM file in bin/[mainfile].rom
if (getCurrentOutput() instanceof Uint8Array) {
let binpath = "bin/" + getCurrentMainFilename() + ".rom";
files.push({ path: binpath, data: getCurrentOutput() });
}
// push files
setWaitDialog(true);
var gh = await getGithubService();
return gh.login().then(() => {
setWaitProgress(0.5);
return gh.commit(ghurl, message, files);
}).then((sess) => {
return confirmCommit(sess);
}).then((sess) => {
return gh.push(sess);
}).then((sess) => {
setWaitDialog(false);
alertInfo("Pushed files to " + ghurl);
return sess;
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not push GitHub repository: " + e);
});
}
export function _removeRepository() {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.prompt("<p>Are you sure you want to delete this repository (" + DOMPurify.sanitize(ghurl) + ") from browser storage?</p><p>All changes since last commit will be lost.</p><p>Type DELETE to proceed.<p>", (yes) => {
if (yes.trim().toUpperCase() == "DELETE") {
removeRepository();
}
});
}
async function removeRepository() {
var ghurl = getBoundGithubURL();
setWaitDialog(true);
let gh = await getGithubService();
let sess = await gh.getGithubSession(ghurl);
gh.bind(sess, false);
// delete all keys in (repo) storage
await getPlatformStore().keys().then((keys: string[]) => {
return Promise.all(keys.map((key) => {
return getPlatformStore().removeItem(key);
}));
});
setWaitDialog(false);
// leave repository
gotoNewLocation(false, { repo: '/' });
}

View File

@ -3,29 +3,30 @@
import * as localforage from "localforage";
import { CodeProject, createNewPersistentStore, LocalForageFilesystem, OverlayFilesystem, ProjectFilesystem, WebPresetsFileSystem } from "./project";
import { WorkerResult, WorkerOutputResult, WorkerError, FileData, WorkerErrorResult } from "../common/workertypes";
import { WorkerResult, WorkerError, FileData } from "../common/workertypes";
import { ProjectWindows } from "./windows";
import { Platform, Preset, DebugSymbols, DebugEvalCondition, isDebuggable, EmuState } from "../common/baseplatform";
import { PLATFORMS, EmuHalt } from "../common/emu";
import { Toolbar } from "./toolbar";
import { getFilenameForPath, getFilenamePrefix, highlightDifferences, byteArrayToString, compressLZG, stringToByteArray,
byteArrayToUTF8, isProbablyBinary, getWithBinary, getBasePlatform, getRootBasePlatform, hex, loadScript, decodeQueryString, parseBool } from "../common/util";
byteArrayToUTF8, isProbablyBinary, getWithBinary, getBasePlatform, getRootBasePlatform, hex, loadScript, decodeQueryString, parseBool, getCookie } from "../common/util";
import { StateRecorderImpl } from "../common/recorder";
import { GHSession, GithubService, getRepos, parseGithubURL } from "./services";
import { getRepos, parseGithubURL } from "./services";
import Split = require('split.js');
import { importPlatform } from "../platform/_index";
import { DisassemblerView, ListingView, PC_LINE_LOOKAHEAD , SourceEditor } from "./views/editors";
import { AddressHeatMapView, BinaryFileView, MemoryMapView, MemoryView, ProbeLogView, ProbeSymbolView, RasterPCHeatMapView, RasterStackMapView, ScanlineIOView, VRAMMemoryView } from "./views/debugviews";
import { AddressHeatMapView, BinaryFileView, MemoryMapView, MemoryView, ProbeLogView, ProbeSymbolView, RasterStackMapView, ScanlineIOView, VRAMMemoryView } from "./views/debugviews";
import { AssetEditorView } from "./views/asseteditor";
import { isMobileDevice } from "./views/baseviews";
import { CallStackView, DebugBrowserView } from "./views/treeviews";
import { saveAs } from "file-saver";
import DOMPurify = require("dompurify");
import { OutputSoundFile, TAPFile } from "../common/audio/CommodoreTape";
import { alertError, alertInfo, fatalError, setWaitDialog, setWaitProgress } from "./dialogs";
import { _importProjectFromGithub, _loginToGithub, _logoutOfGithub, _publishProjectToGithub, _pullProjectFromGithub, _pushProjectToGithub, _removeRepository, importProjectFromGithub } from "./sync";
import { gaEvent, gaPageView } from "./analytics";
import { _downloadAllFilesZipFile, _downloadCassetteFile, _downloadProjectZipFile, _downloadROMImage, _downloadSourceFile, _downloadSymFile, _getCassetteFunction, _recordVideo, _shareEmbedLink } from "./shareexport";
// external libs (TODO)
declare var Tour, GIF, Octokat;
declare var ga;
declare var Tour;
declare var $ : JQueryStatic; // use browser jquery
// query string
@ -50,39 +51,33 @@ interface UIQueryString {
tool?: string;
}
export var qs : UIQueryString = decodeQueryString(window.location.search||'?') as UIQueryString;
/// EXPORTED GLOBALS (TODO: remove)
const isElectron = parseBool(qs.electron);
const isEmbed = parseBool(qs.embed);
/// GLOBALS (TODO: remove)
var PRESETS : Preset[]; // presets array
export var qs = decodeQueryString(window.location.search||'?') as UIQueryString;
export var platform_id : string; // platform ID string (platform)
export var store_id : string; // store ID string (repo || platform)
export var repo_id : string; // repository ID (repo)
export var platform : Platform; // emulator object
var platform_name : string; // platform name (after setPlatformUI)
var toolbar = $("#controls_top");
var uitoolbar : Toolbar;
export var current_project : CodeProject; // current CodeProject object
export var projectWindows : ProjectWindows; // window manager
export var lastDebugState : EmuState; // last debug state (object)
// private globals
var compparams; // received build params from worker
var platform_name : string; // platform name (after setPlatformUI)
var toolbar = $("#controls_top");
var uitoolbar : Toolbar;
var stateRecorder : StateRecorderImpl;
var userPaused : boolean; // did user explicitly pause?
var current_output : any; // current ROM (or other object)
var current_preset : Preset; // current preset object (if selected)
var store : LocalForage; // persistent store
export var compparams; // received build params from worker
export var lastDebugState : EmuState; // last debug state (object)
const isElectron = parseBool(qs.electron);
const isEmbed = parseBool(qs.embed);
type DebugCommandType = null
| 'toline' | 'step' | 'stepout' | 'stepover'
@ -97,6 +92,19 @@ var lastDebugCommand : DebugCommandType = null;
var errorWasRuntime = false;
var lastBreakExpr = "c.PC == 0x6000";
export function getPlatformStore() {
return store;
}
export function getCurrentProject() {
return current_project;
}
export function getCurrentOutput() {
return current_output;
}
export function getWorkerParams() {
return compparams;
}
// TODO: codemirror multiplex support?
// TODO: move to views.ts?
const TOOL_TO_SOURCE_STYLE = {
@ -127,8 +135,11 @@ const TOOL_TO_SOURCE_STYLE = {
'armips': 'vasm',
'ecs': 'ecs',
'remote:llvm-mos': 'text/x-csrc',
'cc7800': 'text/x-csrc',
'armtcc': 'text/x-csrc',
}
// TODO: move into tool class
const TOOL_TO_HELPURL = {
'dasm': 'https://raw.githubusercontent.com/sehugg/dasm/master/doc/dasm.txt',
'cc65': 'https://cc65.github.io/doc/cc65.html',
@ -142,26 +153,7 @@ const TOOL_TO_HELPURL = {
'zmac': "https://raw.githubusercontent.com/sehugg/zmac/master/doc.txt",
'cmoc': "http://perso.b2b2c.ca/~sarrazip/dev/cmoc.html",
'remote:llvm-mos': 'https://llvm-mos.org/wiki/Welcome',
}
function gaEvent(category:string, action:string, label?:string, value?:string) {
if (window['ga']) ga('send', 'event', category, action, label, value);
}
function alertError(s:string) {
setWaitDialog(false);
bootbox.alert({
title: '<span class="glyphicon glyphicon-alert" aria-hidden="true"></span> Alert',
message: DOMPurify.sanitize(s)
});
}
function alertInfo(s:string) {
setWaitDialog(false);
bootbox.alert(DOMPurify.sanitize(s));
}
function fatalError(s:string) {
alertError(s);
throw new Error(s);
'acme': 'https://raw.githubusercontent.com/sehugg/acme/main/docs/QuickRef.txt',
}
function newWorker() : Worker {
@ -445,7 +437,6 @@ async function loadProject(preset_id:string) {
userPrefs.setLastPreset(preset_id);
// load files from storage or web URLs
var result = await current_project.loadFiles([preset_id]);
measureTimeLoad = new Date(); // for timing calc.
if (result && result.length) {
// file found; continue
loadMainWindow(preset_id);
@ -641,435 +632,14 @@ async function getLocalFilesystem(repoid: string) : Promise<ProjectFilesystem> {
}
}
function getCurrentMainFilename() : string {
export function getCurrentMainFilename() : string {
return getFilenameForPath(current_project.mainPath);
}
function getCurrentEditorFilename() : string {
export function getCurrentEditorFilename() : string {
return getFilenameForPath(projectWindows.getActiveID());
}
// GITHUB stuff (TODO: move)
var githubService : GithubService;
function getCookie(name) : string {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
async function getGithubService() {
if (!githubService) {
// load github API client
await loadScript('lib/octokat.js');
// load firebase
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js');
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js');
await loadScript('https://8bitworkshop.com/config.js');
// get github API key from cookie
// TODO: move to service?
var ghkey = getCookie('__github_key');
githubService = new GithubService(Octokat, ghkey, store, current_project);
console.log("loaded github service");
}
return githubService;
}
function getBoundGithubURL() : string {
var toks = (repo_id||'').split('/');
if (toks.length != 2) {
alertError("You are not in a GitHub repository. Choose one from the pulldown, or Import or Publish one.");
return null;
}
return 'https://github.com/' + toks[0] + '/' + toks[1];
}
async function importProjectFromGithub(githuburl:string, replaceURL:boolean) {
var sess : GHSession;
var urlparse = parseGithubURL(githuburl);
if (!urlparse) {
alertError('Could not parse Github URL.');
return;
}
// redirect to repo if exists
var existing = getRepos()[urlparse.repopath];
if (existing && !confirm("You've already imported " + urlparse.repopath + " -- do you want to replace all local files?")) {
return;
}
// create new store for imported repository
setWaitDialog(true);
var newstore = createNewPersistentStore(urlparse.repopath);
// import into new store
setWaitProgress(0.25);
var gh = await getGithubService();
return gh.import(githuburl).then( (sess1:GHSession) => {
sess = sess1;
setWaitProgress(0.75);
return gh.pull(githuburl, newstore);
}).then( (sess2:GHSession) => {
// TODO: only first session has mainPath?
// reload repo
qs = {repo:sess.repopath}; // file:sess.mainPath, platform:sess.platform_id};
setWaitDialog(false);
gaEvent('sync', 'import', githuburl);
gotoNewLocation(replaceURL);
}).catch( (e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not import " + githuburl + "." + e);
});
}
async function _loginToGithub(e) {
var gh = await getGithubService();
gh.login().then(() => {
alertInfo("You are signed in to Github.");
}).catch( (e) => {
alertError("Could not sign in." + e);
});
}
async function _logoutOfGithub(e) {
var gh = await getGithubService();
gh.logout().then(() => {
alertInfo("You are logged out of Github.");
});
}
function _importProjectFromGithub(e) {
var modal = $("#importGithubModal");
var btn = $("#importGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var githuburl = $("#importGithubURL").val()+"";
modal.modal('hide');
importProjectFromGithub(githuburl, false);
});
}
function _publishProjectToGithub(e) {
if (repo_id) {
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");
$("#githubRepoName").val(getFilenamePrefix(getFilenameForPath(current_project.mainPath)));
modal.modal('show');
btn.off('click').on('click', async () => {
var name = $("#githubRepoName").val()+"";
var desc = $("#githubRepoDesc").val()+"";
var priv = $("#githubRepoPrivate").val() == 'private';
var license = $("#githubRepoLicense").val()+"";
var sess;
if (!name) {
alertError("You did not enter a project name.");
return;
}
modal.modal('hide');
setWaitDialog(true);
var gh = await getGithubService();
gh.login().then( () => {
setWaitProgress(0.25);
return gh.publish(name, desc, license, priv);
}).then( (_sess) => {
sess = _sess;
setWaitProgress(0.5);
repo_id = qs.repo = sess.repopath;
return pushChangesToGithub('initial import from 8bitworkshop.com');
}).then( () => {
gaEvent('sync', 'publish', priv?"":name);
importProjectFromGithub(sess.url, false);
}).catch( (e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not publish GitHub repository: " + e);
});
});
}
function _pushProjectToGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
var modal = $("#pushGithubModal");
var btn = $("#pushGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var commitMsg = $("#githubCommitMsg").val()+"";
modal.modal('hide');
pushChangesToGithub(commitMsg);
});
}
function _pullProjectFromGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.confirm("Pull from repository and replace all local files? Any changes you've made will be overwritten.",
async (ok) => {
if (ok) {
setWaitDialog(true);
var gh = await getGithubService();
gh.pull(ghurl).then( (sess:GHSession) => {
setWaitDialog(false);
projectWindows.updateAllOpenWindows(store);
});
}
});
}
function confirmCommit(sess) : Promise<GHSession> {
return new Promise( (resolve, reject) => {
var files = sess.commit.files;
console.log(files);
// anything changed?
if (files.length == 0) {
setWaitDialog(false);
alertInfo("No files changed.");
return;
}
// build commit confirm message
var msg = "";
for (var f of files) {
msg += DOMPurify.sanitize(f.filename) + ": " + f.status;
if (f.additions || f.deletions || f.changes) {
msg += " (" + f.additions + " additions, " + f.deletions + " deletions, " + f.changes + " changes)";
};
msg += "<br/>";
}
// show dialog, continue when yes
bootbox.confirm(msg, (ok) => {
if (ok) {
resolve(sess);
} else {
setWaitDialog(false);
}
});
});
}
async function pushChangesToGithub(message:string) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
// build file list for push
var files = [];
for (var path in current_project.filedata) {
var newpath = current_project.stripLocalPath(path);
var data = current_project.filedata[path];
if (newpath && data) {
files.push({path:newpath, data:data});
}
}
// include built ROM file in bin/[mainfile].rom
if (current_output instanceof Uint8Array) {
let binpath = "bin/"+getCurrentMainFilename()+".rom";
files.push({path:binpath, data:current_output});
}
// push files
setWaitDialog(true);
var gh = await getGithubService();
return gh.login().then( () => {
setWaitProgress(0.5);
return gh.commit(ghurl, message, files);
}).then( (sess) => {
return confirmCommit(sess);
}).then( (sess) => {
return gh.push(sess);
}).then( (sess) => {
setWaitDialog(false);
alertInfo("Pushed files to " + ghurl);
return sess;
}).catch( (e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not push GitHub repository: " + e);
});
}
function _removeRepository() {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.prompt("<p>Are you sure you want to delete this repository (" + DOMPurify.sanitize(ghurl) + ") from browser storage?</p><p>All changes since last commit will be lost.</p><p>Type DELETE to proceed.<p>", (yes) => {
if (yes.trim().toUpperCase() == "DELETE") {
removeRepository();
}
});
}
async function removeRepository() {
var ghurl = getBoundGithubURL();
setWaitDialog(true);
let gh = await getGithubService();
let sess = await gh.getGithubSession(ghurl);
gh.bind(sess, false);
// delete all keys in (repo) storage
await store.keys().then((keys:string[]) => {
return Promise.all(keys.map((key) => {
return store.removeItem(key);
}));
});
setWaitDialog(false);
// leave repository
qs = {repo:'/'};
gotoNewLocation();
}
function _shareEmbedLink(e) {
if (current_output == null) {
alertError("Please fix errors before sharing.");
return true;
}
if (!(current_output instanceof Uint8Array)) {
alertError("Can't share a Verilog executable yet. (It's not actually a ROM...)");
return true;
}
loadClipboardLibrary();
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
var lzgb64 = btoa(byteArrayToString(lzgrom));
var embed = {
p: platform_id,
//n: current_project.mainPath,
r: lzgb64
};
var linkqs = $.param(embed);
var fulllink = get8bitworkshopLink(linkqs, 'player.html');
var iframelink = '<iframe width=640 height=600 src="' + fulllink + '">';
$("#embedLinkTextarea").text(fulllink);
$("#embedIframeTextarea").text(iframelink);
$("#embedLinkModal").modal('show');
$("#embedAdviceWarnAll").hide();
$("#embedAdviceWarnIE").hide();
if (fulllink.length >= 65536) $("#embedAdviceWarnAll").show();
else if (fulllink.length >= 5120) $("#embedAdviceWarnIE").show();
});
return true;
}
function loadClipboardLibrary() {
// can happen in background because it won't be used until user clicks
console.log('clipboard');
import('clipboard').then( (clipmod) => {
let ClipboardJS = clipmod.default;
new ClipboardJS(".btn");
});
}
function get8bitworkshopLink(linkqs : string, fn : string) {
console.log(linkqs);
var loc = window.location;
var prefix = loc.pathname.replace('index.html','');
var protocol = (loc.host == '8bitworkshop.com') ? 'https:' : loc.protocol;
var fulllink = protocol + '//' + loc.host + prefix + fn + '?' + linkqs;
return fulllink;
}
function _downloadCassetteFile_apple2(e) {
var addr = compparams && compparams.code_start;
loadScript('lib/c2t.js').then( () => {
var stdout = '';
var print_fn = function(s) { stdout += s + "\n"; }
var c2t = window['c2t']({
noInitialRun:true,
print:print_fn,
printErr:print_fn
});
var FS = c2t['FS'];
var rompath = getCurrentMainFilename() + ".bin";
var audpath = getCurrentMainFilename() + ".wav";
FS.writeFile(rompath, current_output, {encoding:'binary'});
var args = ["-2bc", rompath+','+addr.toString(16), audpath];
c2t.callMain(args);
var audout = FS.readFile(audpath, {'encoding':'binary'});
if (audout) {
var blob = new Blob([audout], {type: "audio/wav"});
saveAs(blob, audpath);
stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file.";
alertInfo(stdout);
}
});
}
function _downloadCassetteFile_vcs(e) {
loadScript('lib/makewav.js').then( () => {
let stdout = '';
let print_fn = function(s) { stdout += s + "\n"; }
var prefix = getFilenamePrefix(getCurrentMainFilename());
let rompath = prefix + ".bin";
let audpath = prefix + ".wav";
let _makewav = window['makewav']({
noInitialRun:false,
print:print_fn,
printErr:print_fn,
arguments:['-ts', '-f0', '-v10', rompath],
preRun: (mod) => {
let FS = mod['FS'];
FS.writeFile(rompath, current_output, {encoding:'binary'});
}
});
_makewav.ready.then((makewav) => {
let args = [rompath];
makewav.run(args);
console.log(stdout);
let FS = makewav['FS'];
let audout = FS.readFile(audpath, {'encoding':'binary'});
if (audout) {
let blob = new Blob([audout], {type: "audio/wav"});
saveAs(blob, audpath);
stdout += "\nConnect your audio output to the SuperCharger input, turn up the volume, and play the audio file.";
alertInfo(stdout);
}
});
});
}
function _downloadCassetteFile_c64(e) {
var prefix = getFilenamePrefix(getCurrentMainFilename());
let audpath = prefix + ".tap";
let tapmaker = new TAPFile(prefix);
let outfile = new OutputSoundFile({sine_wave:true});
let data = current_output;
let startAddress = data[0] + data[1]*256;
data = data.slice(2); // remove header
tapmaker.setContent({ data, startAddress, type: TAPFile.FILE_TYPE_NON_RELOCATABLE });
tapmaker.generateSound(outfile);
let tapout = outfile.getTAPData();
//let audout = outfile.getSoundData();
if (tapout) {
//let blob = new Blob([audout], { type: "audio/wav" });
let blob = new Blob([tapout], { type: "application/octet-stream" });
saveAs(blob, audpath);
}
}
function _getCassetteFunction() {
switch (getBasePlatform(platform_id)) {
case 'vcs': return _downloadCassetteFile_vcs;
case 'apple2': return _downloadCassetteFile_apple2;
case 'c64': return _downloadCassetteFile_c64;
}
}
function _downloadCassetteFile(e) {
if (current_output == null) {
alertError("Please fix errors before exporting.");
return true;
}
var fn = _getCassetteFunction();
if (fn === undefined) {
alertError("Cassette export is not supported on this platform.");
return true;
}
fn(e);
}
function _revertFile(e) {
var wnd = projectWindows.getActive();
@ -1142,89 +712,22 @@ function _renameFile(e) {
}
}
function _downloadROMImage(e) {
if (current_output == null) {
alertError("Please finish compiling with no errors before downloading ROM.");
return true;
}
var prefix = getFilenamePrefix(getCurrentMainFilename());
if (platform.getDownloadFile) {
var dl = platform.getDownloadFile();
var prefix = getFilenamePrefix(getCurrentMainFilename());
saveAs(dl.blob, prefix + dl.extension);
} else if (current_output instanceof Uint8Array) {
var blob = new Blob([current_output], {type: "application/octet-stream"});
var suffix = (platform.getROMExtension && platform.getROMExtension(current_output))
|| "-" + getBasePlatform(platform_id) + ".bin";
saveAs(blob, prefix + suffix);
} else {
alertError(`The "${platform_id}" platform doesn't have downloadable ROMs.`);
}
}
function _downloadSourceFile(e) {
var text = projectWindows.getCurrentText();
if (!text) return false;
var blob = new Blob([text], {type:"text/plain;charset=utf-8"});
saveAs(blob, getCurrentEditorFilename(), {autoBom:false});
}
async function newJSZip() {
let JSZip = (await import('jszip')).default;
return new JSZip();
}
async function _downloadProjectZipFile(e) {
var zip = await newJSZip();
current_project.iterateFiles( (id, data) => {
if (data) {
zip.file(getFilenameForPath(id), data);
}
});
zip.generateAsync({type:"blob"}).then( (content) => {
saveAs(content, getCurrentMainFilename() + "-" + getBasePlatform(platform_id) + ".zip");
});
}
function _downloadSymFile(e) {
let symfile = platform.getDebugSymbolFile && platform.getDebugSymbolFile();
if (!symfile) {
alertError("This project does not have debug information.");
return;
}
var prefix = getFilenamePrefix(getCurrentMainFilename());
saveAs(symfile.blob, prefix + symfile.extension, {autoBom:false});
}
async function _downloadAllFilesZipFile(e) {
var zip = await newJSZip();
var keys = await store.keys();
setWaitDialog(true);
try {
var i = 0;
await Promise.all(keys.map( (path) => {
return store.getItem(path).then( (text) => {
setWaitProgress(i++/(keys.length+1));
if (text) {
zip.file(path, text as any);
}
});
}));
var content = await zip.generateAsync({type:"blob"});
saveAs(content, getBasePlatform(platform_id) + "-all.zip");
} finally {
setWaitDialog(false);
}
}
function populateExamples(sel) {
var files = {};
sel.append($("<option />").text("--------- Examples ---------").attr('disabled','true'));
let files = {};
let optgroup;
const PRESETS = platform.getPresets ? platform.getPresets() : [];
for (var i=0; i<PRESETS.length; i++) {
var preset = PRESETS[i];
var name = preset.chapter ? (preset.chapter + ". " + preset.name) : preset.name;
var isCurrentPreset = preset.id==current_project.mainPath;
sel.append($("<option />").val(preset.id).text(name).attr('selected',isCurrentPreset?'selected':null));
if (preset.category) {
optgroup = $("<optgroup />").attr('label','Examples: ' + preset.category).appendTo(sel);
} else if (!optgroup) {
optgroup = $("<optgroup />").attr('label','Examples').appendTo(sel);
}
optgroup.append($("<option />").val(preset.id).text(name).attr('selected',isCurrentPreset?'selected':null));
if (isCurrentPreset) current_preset = preset;
files[preset.id] = name;
}
@ -1236,12 +739,11 @@ function populateRepos(sel) {
var n = 0;
var repos = getRepos();
if (repos) {
let optgroup = $("<optgroup />").attr('label','Repositories').appendTo(sel);
for (let repopath in repos) {
var repo = repos[repopath];
if (repo.platform_id && getBasePlatform(repo.platform_id) == getBasePlatform(platform_id)) {
if (n++ == 0)
sel.append($("<option />").text("------ Repositories ------").attr('disabled','true'));
sel.append($("<option />").val(repo.url).text(repo.url.substring(repo.url.indexOf('/'))));
optgroup.append($("<option />").val(repo.url).text(repo.url.substring(repo.url.indexOf('/'))));
}
}
}
@ -1249,16 +751,15 @@ function populateRepos(sel) {
}
async function populateFiles(sel:JQuery, category:string, prefix:string, foundFiles:{}) {
var keys = await store.keys();
var numFound = 0;
let keys = await store.keys();
if (!keys) keys = [];
let optgroup;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
let key = keys[i];
if (key.startsWith(prefix) && !foundFiles[key]) {
if (numFound++ == 0)
sel.append($("<option />").text("------- " + category + " -------").attr('disabled','true'));
var name = key.substring(prefix.length);
sel.append($("<option />").val(key).text(name).attr('selected',(key==current_project.mainPath)?'selected':null));
if (!optgroup) optgroup = $("<optgroup />").attr('label',category).appendTo(sel);
let name = key.substring(prefix.length);
optgroup.append($("<option />").val(key).text(name).attr('selected',(key==current_project.mainPath)?'selected':null));
}
}
}
@ -1343,18 +844,6 @@ function showExceptionAsError(err, msg:string) {
}
}
var measureTimeStart : Date = new Date();
var measureTimeLoad : Date;
function measureBuildTime() {
if (window['ga'] && measureTimeLoad) {
var measureTimeBuild = new Date();
ga('send', 'timing', 'load', platform_id, (measureTimeLoad.getTime() - measureTimeStart.getTime()));
ga('send', 'timing', 'build', platform_id, (measureTimeBuild.getTime() - measureTimeLoad.getTime()));
measureTimeLoad = null; // only measure once
}
//gaEvent('build', platform_id);
}
async function setCompileOutput(data: WorkerResult) {
// errors? mark them in editor
if ('errors' in data && data.errors.length > 0) {
@ -1382,7 +871,6 @@ async function setCompileOutput(data: WorkerResult) {
await platform.loadROM(getCurrentPresetTitle(), rom);
current_output = rom;
if (!userPaused) _resume();
measureBuildTime();
writeOutputROMFile();
} catch (e) {
console.log(e);
@ -1421,13 +909,15 @@ function hideDebugInfo() {
function showDebugInfo(state?) {
if (!isDebuggable(platform)) return;
var meminfo = $("#mem_info");
var meminfomsg = $("#mem_info_msg");
var allcats = platform.getDebugCategories();
if (allcats && !debugCategory)
debugCategory = allcats[0];
var s = state && platform.getDebugInfo(debugCategory, state);
if (s) {
if (typeof s === 'string') {
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
meminfo.show().html(hs);
meminfo.show();
meminfomsg.html(hs);
var catspan = $('<div class="mem_info_links">');
var addCategoryLink = (cat:string) => {
var catlink = $('<a>'+cat+'</a>');
@ -1444,8 +934,8 @@ function showDebugInfo(state?) {
for (var cat of allcats) {
addCategoryLink(cat);
}
meminfo.append('<br>');
meminfo.append(catspan);
meminfomsg.append('<br>');
meminfomsg.append(catspan);
lastDebugInfo = s;
} else {
hideDebugInfo();
@ -1711,75 +1201,6 @@ function updateDebugWindows() {
setTimeout(updateDebugWindows, 100);
}
function setWaitDialog(b : boolean) {
if (b) {
setWaitProgress(0);
$("#pleaseWaitModal").modal('show');
} else {
setWaitProgress(1);
$("#pleaseWaitModal").modal('hide');
}
}
function setWaitProgress(prog : number) {
$("#pleaseWaitProgressBar").css('width', (prog*100)+'%').show();
}
var recordingVideo = false;
function _recordVideo() {
if (recordingVideo) return;
loadScript("lib/gif.js").then( () => {
var canvas = $("#emulator").find("canvas")[0] as HTMLElement;
if (!canvas) {
alertError("Could not find canvas element to record video!");
return;
}
var rotate = 0;
if (canvas.style && canvas.style.transform) {
if (canvas.style.transform.indexOf("rotate(-90deg)") >= 0)
rotate = -1;
else if (canvas.style.transform.indexOf("rotate(90deg)") >= 0)
rotate = 1;
}
var gif = new GIF({
workerScript: 'lib/gif.worker.js',
workers: 4,
quality: 10,
rotate: rotate
});
var img = $('#videoPreviewImage');
gif.on('progress', (prog) => {
setWaitProgress(prog);
});
gif.on('finished', (blob) => {
img.attr('src', URL.createObjectURL(blob));
setWaitDialog(false);
_resume();
$("#videoPreviewModal").modal('show');
});
var intervalMsec = 20;
var maxFrames = 300;
var nframes = 0;
console.log("Recording video", canvas);
$("#emulator").css('backgroundColor', '#cc3333');
var f = () => {
if (nframes++ > maxFrames) {
console.log("Rendering video");
$("#emulator").css('backgroundColor', 'inherit');
setWaitDialog(true);
_pause();
gif.render();
recordingVideo = false;
} else {
gif.addFrame(canvas, {delay: intervalMsec, copy: true});
setTimeout(f, intervalMsec);
recordingVideo = true;
}
};
f();
});
}
export function setFrameRateUI(fps:number) {
platform.setFrameRate(fps);
if (fps > 0.01)
@ -1895,6 +1316,8 @@ function _addIncludeFile() {
addFileToProject("Include", ".wiz", (s) => { return 'import "'+s+'";' });
else if (tool == 'ecs')
addFileToProject("Include", ".ecs", (s) => { return 'import "'+s+'"' });
else if (tool == 'acme')
addFileToProject("Include", ".acme", (s) => { return '!src "'+s+'"' });
else
alertError("Can't add include file to this project type (" + tool + ")");
}
@ -2220,7 +1643,10 @@ function uninstallErrorHandler() {
window.removeEventListener('unhandledrejection', globalErrorHandler);
}
function gotoNewLocation(replaceHistory? : boolean) {
export function gotoNewLocation(replaceHistory? : boolean, newQueryString?: {}) {
if (newQueryString) {
qs = newQueryString;
}
uninstallErrorHandler();
if (replaceHistory)
window.location.replace("?" + $.param(qs));
@ -2289,7 +1715,7 @@ function installGAHooks() {
gaEvent('menu', e.target.id);
}
});
ga('send', 'pageview', location.pathname+'?platform='+platform_id+(repo_id?('&repo='+repo_id):('&file='+qs.file)));
gaPageView(location.pathname+'?platform='+platform_id+(repo_id?('&repo='+repo_id):('&file='+qs.file)));
}
}
@ -2300,7 +1726,7 @@ async function startPlatform() {
platform = new PLATFORMS[platform_id](emudiv, options);
setPlatformUI();
stateRecorder = new StateRecorderImpl(platform);
PRESETS = platform.getPresets ? platform.getPresets() : [];
const PRESETS = platform.getPresets ? platform.getPresets() : [];
if (!qs.file) {
// try to load last file (redirect)
var lastid = userPrefs.getLastPreset();

View File

@ -1,7 +1,7 @@
import { newDiv, ProjectView } from "./baseviews";
import { Segment } from "../../common/workertypes";
import { platform, compparams, current_project, projectWindows, runToPC, setupBreakpoint } from "../ui";
import { platform, current_project, projectWindows, runToPC, setupBreakpoint, getWorkerParams } from "../ui";
import { hex, lpad, rpad } from "../../common/util";
import { VirtualList } from "../../common/vlist";
import { getMousePos, getVisibleEditorLineHeight, VirtualTextLine, VirtualTextScroller } from "../../common/emu";
@ -23,7 +23,8 @@ export class MemoryView implements ProjectView {
dumplines;
maindiv : HTMLElement;
recreateOnResize = true;
totalRows = 0x1400;
hibits = 0; // a hack to make it work with 32-bit addresses
totalRows = 0x1400; // a little more room in case we split lots of lines
createDiv(parent : HTMLElement) {
var div = document.createElement('div');
@ -44,7 +45,7 @@ export class MemoryView implements ProjectView {
var linediv = document.createElement("div");
if (this.dumplines) {
var dlr = this.dumplines[row];
if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a));
if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a | this.hibits));
}
linediv.appendChild(document.createTextNode(s));
return linediv;
@ -52,13 +53,15 @@ export class MemoryView implements ProjectView {
});
$(parent).append(this.memorylist.container);
this.tick();
const compparams = getWorkerParams();
if (compparams && this.dumplines)
this.scrollToAddress(compparams.data_start);
}
scrollToAddress(addr : number) {
if (this.dumplines) {
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr));
this.hibits = addr & 0xffff0000;
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr & 0xffff));
}
}
@ -100,7 +103,7 @@ export class MemoryView implements ProjectView {
for (var i=0; i<n1; i++) s += ' ';
if (n1 > 8) s += ' ';
for (var i=n1; i<n2; i++) {
var read = this.readAddress(offset+i);
var read = this.readAddress((offset+i) | this.hibits);
if (i==8) s += ' ';
s += ' ' + (typeof read == 'number' ? hex(read,2) : '??');
}
@ -129,7 +132,7 @@ export class MemoryView implements ProjectView {
var sym;
for (const _nextofs of Object.keys(addr2sym)) {
var nextofs = parseInt(_nextofs); // convert from string (stupid JS)
var nextsym = addr2sym[nextofs];
var nextsym = addr2sym[nextofs | this.hibits];
if (sym) {
// ignore certain symbols
if (ignoreSymbol(sym)) {
@ -151,6 +154,7 @@ export class MemoryView implements ProjectView {
// TODO: use segments list?
getMemorySegment(a:number) : string {
const compparams = getWorkerParams();
if (compparams) {
if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
if (platform.getSP && a >= platform.getSP() - 15)
@ -245,6 +249,8 @@ export class MemoryMapView implements ProjectView {
this.maindiv = newDiv(parent, 'vertical-scroll');
this.maindiv.css('display', 'grid');
this.maindiv.css('grid-template-columns', '5em 40% 40%');
//this.maindiv.css('grid-template-rows', '2em auto auto');
this.maindiv.css('align-content', 'start');
return this.maindiv[0];
}
@ -256,17 +262,15 @@ export class MemoryMapView implements ProjectView {
this.maindiv.append(offset);
}
var segdiv = $('<div class="segment"/>');
if (!newrow)
segdiv.text(seg.name);
let alttext = `$${hex(seg.start)} - $${hex(seg.last || seg.start+seg.size-1)}`
alttext += ` (${seg.size} bytes)`;
// set alttext of div
segdiv.attr('title', alttext);
if (!newrow || seg.source == 'linker')
segdiv.css('grid-column-start', 3); // make sure it's on right side
if (seg.last)
segdiv.text(seg.name+" ("+(seg.last-seg.start)+" / "+seg.size+" bytes used)");
else
segdiv.text(seg.name+" ("+seg.size+" bytes)");
if (seg.size >= 256) {
var pad = (Math.log(seg.size) - Math.log(256)) * 0.5;
segdiv.css('padding-top', pad+'em');
segdiv.css('padding-bottom', pad+'em');
}
var pad = Math.max(3.0, Math.log(seg.size+1)) * 0.5;
segdiv.css('height', pad+'em');
if (seg.type) {
segdiv.addClass('segment-'+seg.type);
}
@ -288,9 +292,10 @@ export class MemoryMapView implements ProjectView {
var curofs = 0;
var laststart = -1;
for (var seg of segments) {
//var used = seg.last ? (seg.last-seg.start) : seg.size;
if (seg.start > curofs)
this.addSegment({name:'',start:curofs, size:seg.start-curofs}, true);
// add free space
if (seg.start > curofs) {
this.addSegment({ name: '', start: curofs, size: seg.start - curofs }, true);
}
this.addSegment(seg, laststart != seg.start);
laststart = seg.start;
curofs = seg.start + seg.size;

View File

@ -2,13 +2,7 @@
import { MOS6502, MOS6502State } from "../common/cpu/MOS6502";
import { Bus, BasicScanlineMachine, SavesState, AcceptsBIOS } from "../common/devices";
import { KeyFlags } from "../common/emu"; // TODO
import { hex, lzgmini, stringToByteArray, RGBA, printFlags } from "../common/util";
// TODO: read prodos/ca65 header?
const VM_BASE = 0x803; // where to JMP after pr#6
const LOAD_BASE = VM_BASE;
const PGM_BASE = VM_BASE;
const HDR_SIZE = PGM_BASE - LOAD_BASE;
import { hex, lzgmini, stringToByteArray, RGBA, printFlags, arrayCompare } from "../common/util";
interface AppleIIStateBase {
ram : Uint8Array;
@ -43,7 +37,11 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
canvasWidth = 280;
numVisibleScanlines = 192;
numTotalScanlines = 262;
defaultROMSize = 0xbf00-0x803; // TODO
defaultROMSize = 0x13000; // we'll never need one that big, but...
// these are set later
LOAD_BASE = 0;
HDR_SIZE = 0;
ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM
bios : Uint8Array;
@ -78,8 +76,8 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
// SHOULD load program into RAM here, but have to do it
// below instead.
return 0;
case 1: return VM_BASE&0xff;
case 2: return (VM_BASE>>8)&0xff;
case 1: return this.LOAD_BASE&0xff;
case 2: return (this.LOAD_BASE>>8)&0xff;
default: return 0;
}
}
@ -97,8 +95,7 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
// into RAM and returning the JMP here, instead of above
// where it would otherwise belong.
if (this.rom) {
console.log(`Loading program into Apple ][ RAM at \$${PGM_BASE.toString(16)}`);
this.ram.set(this.rom.slice(HDR_SIZE), PGM_BASE);
this.loadRAMWithProgram();
}
return 0x4c; // JMP
case 1: return 0x20;
@ -168,26 +165,57 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
console.log("will load BIOS to end of memory anyway...");
}
this.bios = Uint8Array.from(data);
this.ram.set(this.bios, 0x10000 - this.bios.length);
this.ram[0xbf00] = 0x4c; // fake DOS detect for C
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
}
loadROM(data) {
if (data.length == 35*16*256) { // is it a disk image?
var diskii = new DiskII(this, data);
this.slots[6] = diskii;
} else { // it's a binary, use a fake drive
super.loadROM(data);
this.slots[6] = this.fakeDrive;
loadROM(data) {
// is it a 16-sector 35-track disk image?
if (data.length == 16 * 35 * 256) {
var diskii = new DiskII(this, data);
this.slots[6] = diskii;
this.reset();
} else { // it's a binary, use a fake drive
// set this.rom variable
super.loadROM(data);
// AppleSingle header? https://github.com/cc65/cc65/blob/master/libsrc/apple2/exehdr.s
if (arrayCompare(this.rom.slice(0, 4), [0x00, 0x05, 0x16, 0x00])) {
this.LOAD_BASE = this.rom[0x39] | (this.rom[0x38] << 8); // big endian
this.HDR_SIZE = 58;
} else {
// 4-byte DOS header? (TODO: hacky detection)
const origin = this.rom[0] | (this.rom[1] << 8);
const size = this.rom[2] | (this.rom[3] << 8);
let isPlausible = origin < 0xc000
&& origin + size < 0x13000
&& (origin == 0x803 || (origin & 0xff) == 0);
if (size == data.length - 4 && isPlausible) {
this.LOAD_BASE = origin;
this.HDR_SIZE = 4;
} else {
// default = raw binary @ $803
this.LOAD_BASE = 0x803;
this.HDR_SIZE = 0;
}
}
this.slots[6] = this.fakeDrive;
}
}
loadRAMWithProgram() {
console.log(`Loading program into Apple ][ RAM at \$${this.LOAD_BASE.toString(16)}`);
// truncate if needed to fit into RAM
const exedata = this.rom.slice(this.HDR_SIZE, this.HDR_SIZE + this.ram.length - this.LOAD_BASE);
this.ram.set(exedata, this.LOAD_BASE);
// fake DOS detect for CC65 (TODO?)
if (this.HDR_SIZE == 58) {
this.ram[0xbf00] = 0x4c;
this.ram[0xbf6f] = 0x01;
}
}
}
reset() {
super.reset();
this.auxRAMselected = false;
this.auxRAMbank = 1;
this.writeinhibit = true;
this.ram.fill(0, 0x300, 0x400); // Clear soft-reset vector
// (force hard reset)
super.reset();
this.skipboot();
}
skipboot() {
@ -224,6 +252,7 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
if (address < 0xc000 || address >= 0xd000) {
return this.readConst(address);
} else if (address < 0xc100) {
this.probe.logIORead(address, 0); // TODO: value
var slot = (address >> 4) & 0x0f;
switch (slot)
{
@ -285,6 +314,7 @@ export class AppleII extends BasicScanlineMachine implements AcceptsBIOS {
} else if (address < 0xc100) {
var slot = (address >> 4) & 0x0f;
this.slots[slot-8] && this.slots[slot-8].write(address & 0xf, val);
this.probe.logIOWrite(address, val);
} else if (address >= 0xd000 && !this.writeinhibit) {
if (address >= 0xe000)
this.ram[address] = val;
@ -477,7 +507,7 @@ var Apple2Display = function(pixels : Uint32Array, apple : AppleGRParams) {
var oldgrmode = -1;
var textbuf = new Array(40*24);
const flashInterval = 500;
const flashInterval = 250;
// https://mrob.com/pub/xapple2/colors.html
const loresColor = [

View File

@ -1,6 +1,27 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { ARM32CPU, ARMCoreState } from "../common/cpu/ARM";
import { BasicScanlineMachine, HasSerialIO, SerialEvent, SerialIOInterface } from "../common/devices";
import { BasicScanlineMachine, Bus32, HasSerialIO, SerialEvent, SerialIOInterface } from "../common/devices";
import { newAddressDecoder, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
import { Debuggable, EmuState } from "../common/baseplatform";
import { hex, lpad } from "../common/util";
@ -18,36 +39,41 @@ var GBA_KEYCODE_MAP = makeKeycodeMap([
[Keys.DOWN, 0, 0x80],
]);
const ROM_START = 0x0;
const ROM_SIZE = 0x80000;
const RAM_START = 0x2000000;
const RAM_SIZE = 0x80000;
const RAM_START = 0x0;
const RAM_SIZE = 0x100000;
const ROM_BASE = 0x0;
const IO_START = 0x4000000;
const IO_SIZE = 0x100;
const MAX_SERIAL_CHARS = 1000000;
const CPU_FREQ = 4000000; // 4 MHz
export class ARM32Machine extends BasicScanlineMachine implements Debuggable, HasSerialIO {
const ILLEGAL_OPCODE = 0xedededed;
export class ARM32Machine extends BasicScanlineMachine
implements Debuggable, HasSerialIO, Bus32 {
cpuFrequency = CPU_FREQ; // MHz
canvasWidth = 160;
numTotalScanlines = 256;
numVisibleScanlines = 128;
cpuCyclesPerLine = Math.floor(CPU_FREQ / (256*60));
defaultROMSize = 512*1024;
defaultROMSize = RAM_SIZE - ROM_BASE;
sampleRate = 1;
cpu: ARM32CPU = new ARM32CPU();
ram = new Uint8Array(96*1024);
ram = new Uint8Array(RAM_SIZE);
ram16 = new Uint16Array(this.ram.buffer);
ram32 = new Uint32Array(this.ram.buffer);
pixels32 : Uint32Array;
pixels8 : Uint8Array;
vidbase : number = 0;
rombase : number = ROM_BASE;
brightness : number = 255;
serial : SerialIOInterface;
serialOut : SerialEvent[];
serialIn : SerialEvent[];
ioregs = new Uint8Array(IO_SIZE);
ioregs32 = new Uint32Array(this.ioregs.buffer);
constructor() {
super();
@ -65,7 +91,15 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
this.serial = serial;
}
loadROM(rom: Uint8Array) {
super.loadROM(rom);
}
reset() {
this.ram.fill(0);
if (this.rom) {
this.ram.set(this.rom, this.rombase);
}
super.reset();
this.serialOut = [];
this.serialIn = [];
@ -74,19 +108,13 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
// TODO: 32-bit bus?
read = newAddressDecoder([
[ROM_START, ROM_START+ROM_SIZE-1, ROM_SIZE-1, (a) => {
return this.rom ? this.rom[a] : 0;
}],
[RAM_START, RAM_START+RAM_SIZE-1, RAM_SIZE-1, (a) => {
return this.ram[a];
}],
[IO_START, IO_START+IO_SIZE-1, IO_SIZE-1, (a, v) => {
return this.readIO(a);
}],
[0, (1<<31)-1, 0, (a, v) => {
throw new EmuHalt(`Address read out of bounds: 0x${hex(a)}`);
}]
]);
], {defaultval: ILLEGAL_OPCODE & 0xff});
write = newAddressDecoder([
[RAM_START, RAM_START+RAM_SIZE-1, RAM_SIZE-1, (a, v) => {
@ -97,10 +125,42 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}],
]);
read32 = (a) => {
if (a >= RAM_START && a < RAM_SIZE && (a & 3) == 0) {
return this.ram32[a >> 2];
} else {
return this.read(a) | (this.read(a+1)<<8) | (this.read(a+2)<<16) | (this.read(a+3)<<24);
}
};
write32 = (a, v) => {
if (a >= RAM_START && a < RAM_SIZE && (a & 3) == 0) {
this.ram32[a >> 2] = v;
} else {
this.write(a, v & 0xff);
this.write(a+1, (v>>8) & 0xff);
this.write(a+2, (v>>16) & 0xff);
this.write(a+3, (v>>24) & 0xff);
}
}
readAddress(a : number) : number {
if (a >= RAM_START && a < RAM_START+RAM_SIZE) return this.read(a);
else return ILLEGAL_OPCODE;
}
readIO(a : number) : number {
switch (a) {
case 0x0:
return this.inputs[0];
case 0x20:
return this.getRasterY() & 0xff;
case 0x21:
return this.getRasterY() >> 8;
case 0x24:
return this.getRasterX();
case 0x25:
return this.getRasterX() >> 8;
case 0x40:
return (this.serial.byteAvailable() ? 0x80 : 0) | (this.serial.clearToSend() ? 0x40 : 0);
case 0x44:
@ -116,16 +176,14 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}
writeIO(a : number, v : number) : void {
this.ioregs[a] = v;
switch (a) {
case 0x0:
//this.brightness = v & 0xff;
break;
case 0x48:
if (this.serialOut.length < MAX_SERIAL_CHARS) {
this.serialOut.push({op:'write', value:v, nbits:8});
}
break;
}
}
}
startScanline() {
@ -136,7 +194,8 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
postFrame() {
var p32 = this.pixels32;
var vbase = (this.vidbase >> 1) & 0xfffff;
const vidbase = this.ioregs32[0x80 >> 2];
var vbase = (vidbase >> 1) & 0xfffff;
var mask = this.brightness << 24;
for (var i=0; i<p32.length; i++) {
var col = this.ram16[i + vbase];
@ -147,18 +206,34 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}
getDebugCategories() {
return ['CPU', 'Stack'];
return ['CPU', 'Stack', 'FPU'];
}
getDebugInfo?(category: string, state: EmuState) : string {
switch (category) {
case 'Stack':
var s = '';
var c = state.c as ARMCoreState;
var sp = c.gprs[13];
var fp = c.gprs[11];
// dump stack using ram32
for (var i=0; i<16; i++) {
s += hex(sp,8) + ' ' + hex(this.ram32[(sp-RAM_START)>>2],8);
if (sp == fp) s += ' FP';
s += '\n';
sp += 4;
if (sp >= RAM_START+RAM_SIZE) break;
}
return s;
case 'CPU':
var s = '';
var c = state.c as ARMCoreState;
const EXEC_MODE = {2:'Thumb',4:'ARM'};
const REGNAMES = {15:'PC',14:'LR',13:'SP',12:'IP',11:'FP',9:'SB'};
for (var i=0; i<16; i++) {
s += lpad(REGNAMES[i]||'',3) + lpad('r'+i, 5) + ' ' + hex(c.gprs[i],8) + '\n';
for (var i=0; i<8; i++) {
let j = i+8;
s += lpad('r'+i, 5) + ' ' + hex(c.gprs[i],8) + ' ';
s += lpad('r'+j, 5) + ' ' + hex(c.gprs[j],8) + lpad(REGNAMES[j]||'',3) + '\n';
}
s += 'Flags ';
s += c.cpsrN ? " N" : " -";
@ -172,6 +247,19 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
s += 'SPSR ' + hex(c.spsr,8) + '\n';
s += 'cycl ' + c.cycles + '\n';
return s;
case 'FPU':
var s = '';
var c = state.c as ARMCoreState;
for (var i=0; i<16; i++) {
//let j = i+16;
s += lpad('s'+i, 5) + ' ' + hex(c.ifprs[i],8) + ' ' + c.sfprs[i].toPrecision(6);
if (i & 1) {
s += lpad('d'+(i>>1), 5) + ' ' + c.dfprs[i>>1].toPrecision(12);
}
s += '\n';
//s += lpad('s'+j, 5) + ' ' + lpad(c.sfprs[j]+'',8) + '\n';
}
return s;
}
}

View File

@ -71,6 +71,7 @@ const _BallyAstrocade = function(arcade:boolean) {
const swidth = arcade ? 320 : 160;
const sheight = arcade ? 204 : 102;
const swbytes = swidth >> 2;
const samask = arcade ? 0x3fff : 0xfff;
const INITIAL_WATCHDOG = 256;
const PIXEL_ON = 0xffeeeeee;
const PIXEL_OFF = 0xff000000;
@ -90,19 +91,19 @@ const _BallyAstrocade = function(arcade:boolean) {
var palinds = new Uint8Array(8);
var refreshlines = 0;
var dirtylines = new Uint8Array(arcade ? 262 : 131);
var vidactive = false;
var rotdata = new Uint8Array(4);
var rotcount = 0;
var intst = 0;
var waitstates = 0;
var patboard = new Uint8Array(0x08);
var patdest = 0;
function ramwrite(a: number, v: number) {
// set RAM
ram[a] = v;
waitstates++;
// mark scanline as dirty
dirtylines[((a & 0xfff) / swbytes) | 0] = 1;
dirtylines[((a & samask) / swbytes) | 0] = 1;
// this was old behavior where we updated instantly
// but it had problems if we had mid-screen palette changes
//ramupdate(a, v);
@ -201,7 +202,105 @@ const _BallyAstrocade = function(arcade:boolean) {
function refreshall() {
refreshlines = sheight;
}
// bally astrocade pattern board
// https://github.com/mamedev/mame/blob/7ff10685c56a6e123336c684e5e96fdb9e8b3674/src/mame/midway/astrocde_v.cpp#L726
function xfer_patboard() {
let m_pattern_source = patboard[0] | (patboard[1] << 8);
let m_pattern_mode = patboard[2] & 0x3f;
let m_pattern_skip = patboard[3];
let m_pattern_dest = (patdest & 0xff) | (patboard[4] << 8);
let m_pattern_width = patboard[5];
let m_pattern_height = patboard[6] + 1;
let curwidth: number;
let u13ff: number = 0;
let cycles: number = 0;
u13ff = 0;
if ((m_pattern_mode & 0x02) === 0) {
u13ff = 1;
}
function incrementSource(): void {
if (u13ff && (m_pattern_mode & 0x04) !== 0 && (curwidth !== 0 || (m_pattern_mode & 0x08) === 0)) {
m_pattern_source++;
}
if ((m_pattern_mode & 0x02) !== 0) {
u13ff ^= 1;
}
}
function incrementDest(): void {
if (curwidth !== 0) {
if ((m_pattern_mode & 0x20) !== 0) {
m_pattern_dest++;
} else {
m_pattern_dest--;
}
}
}
// Loop over height
while (m_pattern_height >= 0) {
let carry: number;
curwidth = m_pattern_width;
// Loop over width
while (curwidth >= 0) {
let busaddr: number;
let busdata: number;
// Read Phase
busaddr = (m_pattern_mode & 0x01) === 0 ? m_pattern_source : m_pattern_dest;
if (curwidth === 0 && (m_pattern_mode & 0x08) !== 0) {
busdata = 0;
} else {
busdata = membus.read(m_pattern_source);
}
if ((m_pattern_mode & 0x01) === 0) {
incrementSource();
} else {
incrementDest();
}
// Write Phase
busaddr = (m_pattern_mode & 0x01) !== 0 ? m_pattern_source : m_pattern_dest;
ramwrite(busaddr, busdata);
if ((m_pattern_mode & 0x01) === 0) {
incrementDest();
} else {
incrementSource();
}
cycles += 4;
curwidth--;
}
// At the end of each row, adjust m_pattern_dest
carry = ((m_pattern_dest & 0xff) + m_pattern_skip) & 0x100;
m_pattern_dest = (m_pattern_dest & 0xff00) | ((m_pattern_dest + m_pattern_skip) & 0xff);
if ((m_pattern_mode & 0x10) === 0) {
m_pattern_dest += carry;
} else {
m_pattern_dest -= carry ^ 0x100;
}
m_pattern_height--;
}
// Adjust m_maincpu.icount
// m_maincpu.adjust_icount(-cycles);
// Replace the above line with the actual adjustment of icount.
}
this.drawScanline = (sl:number) => {
// interrupt
if (sl == inlin && (inmod & 0x8)) {
@ -226,9 +325,9 @@ const _BallyAstrocade = function(arcade:boolean) {
ram = r;
inputs = inp;
psg = psgg;
//bios = padBytes(ASTROCADE_MINIMAL_BIOS, 0x2000);
bios = padBytes(new lzgmini().decode(stringToByteArray(atob(ASTROLIBRE_BIOS_LZG))), 0x2000);
if (!arcade) {
//bios = padBytes(ASTROCADE_MINIMAL_BIOS, 0x2000);
bios = padBytes(new lzgmini().decode(stringToByteArray(atob(ASTROLIBRE_BIOS_LZG))), 0x2000);
// game console
membus = {
read: newAddressDecoder([
@ -246,13 +345,14 @@ const _BallyAstrocade = function(arcade:boolean) {
membus = {
read: newAddressDecoder([
[0x4000, 0x7fff, 0x3fff, function(a) { return ram[a]; }], // screen RAM
[0xd000, 0xdfff, 0xfff, function(a) { return ram[a + 0x4000]; }], // static RAM
[0x0000, 0xafff, 0xffff, function(a) { return rom ? rom[a] : 0; }], // ROM (0-3fff,8000-afff)
[0xd000, 0xdfff, 0x0fff, function(a) { return ram[a + 0x4000]; }], // static RAM
[0x0000, 0x3fff, 0x3fff, function(a) { return rom ? rom[a] : 0; }], // ROM
[0x8000, 0xbfff, 0x3fff, function(a) { return rom ? rom[a + 0x4000] : 0; }], // ROM
]),
write: newAddressDecoder([
[0x4000, 0x7fff, 0x3fff, ramwrite],
[0xd000, 0xdfff, 0xfff, function(a, v) { ramwrite(a + 0x4000, v); }], // static RAM
[0x0000, 0x3fff, 0x3fff, magicwrite],
[0xd000, 0xdfff, 0x0fff, function(a, v) { ramwrite(a + 0x4000, v); }], // static RAM
]),
}
}
@ -273,6 +373,19 @@ const _BallyAstrocade = function(arcade:boolean) {
return rtn;
},
write: function(addr, val) {
if (addr == 0xa55b) {
// TODO: protected_ram_enable_w
return;
}
addr &= 0xff;
// pattern board
if (addr > 0x78 && addr < 0x80) {
patboard[addr & 7] = val;
if (addr == 0x72) { patdest = 0; }
if (addr == 0x74) { patdest = (patdest + patboard[3]) & 0xff; }
if (addr == 0x7e) { xfer_patboard(); }
return;
}
addr &= 0x1f;
val &= 0xff;
switch (addr) {
@ -332,8 +445,15 @@ const _BallyAstrocade = function(arcade:boolean) {
case 0x19: // XPAND
xpand = val;
break;
case 0x1a:
case 0x1b:
case 0x1c:
case 0x1d:
case 0x1e:
//psg2.setACRegister(addr - 0x1a, val);
break;
default:
console.log('IO write', hex(addr, 4), hex(val, 2));
//console.log('IO write', hex(addr, 4), hex(val, 2));
break;
}
}
@ -372,6 +492,8 @@ const _BallyAstrocade = function(arcade:boolean) {
rotdata.set(state.rotdata);
intst = state.intst;
inputs.set(state.inputs);
patboard.set(state.patboard);
patdest = state.patdest;
refreshall();
}
@ -393,7 +515,9 @@ const _BallyAstrocade = function(arcade:boolean) {
verbl: verbl,
rotcount: rotcount,
rotdata: rotdata.slice(0),
intst: intst
intst: intst,
patboard: patboard.slice(0),
patdest: patdest,
};
}
this.reset = () => {
@ -450,6 +574,7 @@ export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddl
ram : Uint8Array;
cpu : Z80;
m; // _BallyAstrocade
arcade : boolean;
psg: AstrocadeAudio;
audioadapter;
@ -458,6 +583,7 @@ export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddl
constructor(arcade:boolean) {
super();
this.arcade = arcade;
this.cpu = new Z80();
this.psg = new AstrocadeAudio(new MasterAudio());
this.audioadapter = new TssChannelAdapter(this.psg.psg, audioOversample, this.sampleRate);
@ -471,6 +597,12 @@ export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddl
//this.cpuCyclesPerVisible = this.cpuCyclesPerLine - this.cpuCyclesPerHBlank;
this.m = new _BallyAstrocade(arcade);
this.m.init(this, this.cpu, this.ram, this.inputs, this.psg);
if (arcade) {
this.inputs[0x10] = 0xff; // switches (active low)
this.inputs[0x11] = 0xff; // switches (active low)
this.inputs[0x12] = 0x00;
this.inputs[0x13] = 0x08; // dip switches
}
}
read(a:number) : number {
@ -556,7 +688,16 @@ export class BallyAstrocade extends BasicScanlineMachine implements AcceptsPaddl
case 'Astro': return this.m.toLongString(state);
}
}
getRasterCanvasPosition() {
return { x: this.getRasterX(), y: this.getRasterY() };
}
getVideoParams() {
if (this.arcade) {
return { width: 320, height: 204, rotate: 180 };
} else {
return { width: 160, height: 102 };
}
}
}
/////
@ -590,16 +731,6 @@ class AstrocadeAudio extends AY38910_Audio {
}
}
export const _BallyArcade = function() {
this.__proto__ = new (_BallyAstrocade as any)(true);
// TODO: inputs[0x13] = 0xfe; // dip switch on arcade
// TODO: arcade controls, bit blit
var _in = this.saveControlsState();
_in.in[0x10] = 0xff; // switches
_in.in[0x13] = 0xfe; // dip switches
this.loadControlsState(_in);
}
/////
//http://glankonian.com/~lance/astrocade_palette.html

View File

@ -516,6 +516,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
var mc = 0;
var fc = 0;
var steps = 0;
this.lastFrameCycles = -1;
this.probe.logNewFrame();
//console.log(hex(this.cpu.getPC()), hex(this.maria.dll));
// visible lines
@ -530,6 +531,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
if (trap && trap()) {
trap = null;
sl = 999;
this.lastFrameCycles = mc;
break; // TODO?
}
mc += this.advanceCPU() << 2;
@ -567,6 +569,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
if (trap && trap()) {
trap = null;
sl = 999;
this.lastFrameCycles = mc;
break;
}
mc += this.advanceCPU() << 2;
@ -583,13 +586,18 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// TODO let bkcol = this.maria.regs[0x0];
// TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
*/
this.lastFrameCycles = fc;
return steps;
}
getRasterX() { return this.lastFrameCycles % colorClocksPerLine; }
// TODO: doesn't work when breakpoint
getRasterX() { return (this.lastFrameCycles + colorClocksPerLine) % colorClocksPerLine; }
getRasterY() { return this.scanline; }
getRasterCanvasPosition() {
return { x: this.getRasterX(), y: this.getRasterY() };
}
loadROM(data) {
if (data.length == 0xc080) data = data.slice(0x80); // strip header
this.rom = padBytes(data, this.defaultROMSize, true);

View File

@ -267,12 +267,18 @@ export class Atari800 extends BasicScanlineMachine implements AcceptsPaddleInput
keycode: this.keycode,
};
}
getRasterScanline() {
getRasterY() {
return this.antic.v;
}
getRasterLineClock() {
getRasterX() {
return this.antic.h;
}
getRasterCanvasPosition() {
return {
x: this.antic.h * 4 - this.firstVisibleClock,
y: this.antic.v - this.firstVisibleScanline,
}
}
getDebugCategories() {
return ['CPU', 'Stack', 'ANTIC', 'GTIA', 'POKEY'];
}

View File

@ -200,9 +200,18 @@ export class C64_WASMMachine extends BaseWASMMachine
}
this.exports.c64_joystick(this.sys, this.joymask0, this.joymask1);
}
getRasterX() {
return this.statearr[0xf4];
}
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: this.getRasterX() * 392/63,
y: this.getRasterY() - 14,
}
}
getDebugStateOffset(index: number) {
var p = this.exports.machine_get_debug_pointer(this.sys, index);
return p - this.sys;

View File

@ -69,6 +69,12 @@ export class CPC_WASMMachine extends BaseWASMMachine implements Machine {
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: -1, // TODO?
y: this.getRasterY() - 14,
}
}
/*
z80_tick_t tick_cb; // 0
uint64_t bc_de_hl_fa; // 8

331
src/machine/exidy.ts Normal file
View File

@ -0,0 +1,331 @@
import { MOS6502 } from "../common/cpu/MOS6502";
import { BasicScanlineMachine, CPU } from "../common/devices";
import { KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu";
// https://github.com/mamedev/mame/blob/e9ac85ca86a873c67d6bc8d7cf2e37bc7696b379/src/mame/exidy/exidy.cpp#L327
// http://www.arcaderestoration.com/games/9089/Targ.aspx
// http://www.arcaderestoration.com/memorymap/9089/Targ.aspx
// http://www.arcaderestoration.com/memorymap/3933/Hard+Hat.aspx
// https://github.com/mamedev/mame/blob/74c4a0c3774e3aeb4895eb13f3c47773d34ce270/src/mame/shared/exidysound.cpp#L13
const EXIDY_KEYCODE_MAP = makeKeycodeMap([
[Keys.START, 1, -0x1],
//[Keys.START, 1, -0x2],
[Keys.RIGHT, 1, -0x4],
[Keys.LEFT, 1, -0x8],
[Keys.A, 1, -0x10],
[Keys.UP, 1, -0x20],
[Keys.DOWN, 1, -0x40],
[Keys.SELECT, 1, -0x80],
]);
/*
ROM layout:
0x0000 - 0x5fff: program ROM
0x6000 - 0x67ff: sprite ROM
0x6800 - 0x7fff: audio ROM
*/
export class ExidyUGBv2 extends BasicScanlineMachine {
cpuFrequency = 705562;
sampleRate = 894886;
numVisibleScanlines = 256;
numTotalScanlines = 262;
cpuCyclesPerLine = 0x150 >> 3;
canvasWidth = 256;
defaultROMSize = 0x8000 + 0x800 + 0x2800; // PRG + CHR + SOUND
cpu = new MOS6502();
ram = new Uint8Array(0x7000);
color_latch = [0x54, 0xee, 0x6b]; // RGB
palette = [
0xff000000, 0xff0000ff, 0xffff0000, 0xffff00ff,
0xff00ff00, 0xff00ffff, 0xffffff00, 0xffffffff,
];
sprite_gfx: Uint8Array;
inputs = new Uint8Array(4);
keyMap = EXIDY_KEYCODE_MAP;
handler = newKeyboardHandler(this.inputs, this.keyMap); /*, (o,k,c,f) => {
// coin inserted?
if (o.index == 1 && o.mask == 128 && (f & KeyFlags.KeyDown)) {
this.inputs[3] |= 0x40;
//this.cpu.IRQ();
//this.ram[0xa2] += 8; // TODO
}
});
*/
scrnbase = 0x4000;
charbase = 0x6800;
bus = {
read: newAddressDecoder([
[0x0000, 0x03ff, 0, (a) => { return this.ram[a]; }],
[0x1000, 0x3fff, 0, (a) => { return this.rom[a - 0x1000]; }],
[0x4000, 0x4fff, 0, (a) => { return this.ram[a]; }],
[0x5100, 0x51ff, 0x03, (a) => { return a == 3 ? this.int_latch() : this.inputs[a] }],
[0x6000, 0x6fff, 0, (a) => { return this.ram[a]; }],
[0x8000, 0xffff, 0, (a) => { return this.rom[a - 0x8000]; }],
]),
write: newAddressDecoder([
[0x0000, 0x03ff, 0, (a, v) => { this.ram[a] = v; }],
[0x4000, 0x4fff, 0, (a, v) => { this.ram[a] = v; }],
[0x5000, 0x5101, 0, (a, v) => { this.ram[a] = v; }], // TODO: sprite latch
[0x5210, 0x5212, 3, (a, v) => { this.setColorLatch(a, v); }],
[0x6000, 0x6fff, 0, (a, v) => { this.ram[a] = v; }],
]),
}
constructor() {
super();
this.connectCPUMemoryBus(this);
this.updatePalette();
this.inputs[0] = 0b11101010; // dip switch
this.inputs[1] = 0b11111111; // active low
}
loadROM(rom: Uint8Array) {
super.loadROM(rom);
if (rom.length < 0x8000) {
if (rom.length == 11616) { // targ
this.rom.set(rom.slice(0x2800, 0x3000), 0x8000); // copy sprites
this.rom.set(rom.slice(0x2700, 0x2800), 0x7f00); // copy ff00-ffff
this.rom.set(rom.slice(0x0000, 0x2800), 0x0800); // ROM starts @ 0x1800
this.scrnbase = 0x4000;
this.charbase = 0x4800;
} else if (rom.length == 14336) { // spectar
this.rom.set(rom.slice(0x3400, 0x3800), 0x8000); // copy sprites
this.rom.set(rom.slice(0x2f00, 0x3000), 0x7f00); // copy ff00-ffff
this.scrnbase = 0x4000;
this.charbase = 0x4800;
} else {
console.log("Warning: ROM is too small", rom.length);
}
}
let sprite_ofs = 0x8000;
this.sprite_gfx = this.rom.subarray(sprite_ofs, sprite_ofs + 32 * 32);
}
read(a: number): number {
return this.bus.read(a);
}
readConst(a: number): number {
if (a == 0x5103) return this.inputs[3];
return this.bus.read(a);
}
write(a: number, v: number): void {
this.bus.write(a, v);
}
int_latch() {
let intsrc = this.inputs[3];
intsrc |= (this.inputs[1] & 0x80) ? 0 : 0x40; // coin 1
this.inputs[3] = 0x80; // clear int latch
return intsrc; // TODO
}
updatePalette() {
/* motion object 1 */
this.set_1_color(0, 0);
this.set_1_color(1, 7);
/* motion object 2 */
this.set_1_color(2, 0);
this.set_1_color(3, 6);
/* characters */
this.set_1_color(4, 4);
this.set_1_color(5, 3);
this.set_1_color(6, 2);
this.set_1_color(7, 1);
}
set_1_color(index: number, dipsw: number) {
let r = (this.color_latch[0] & (1 << dipsw)) ? 1 : 0;
let g = (this.color_latch[1] & (1 << dipsw)) ? 2 : 0;
let b = (this.color_latch[2] & (1 << dipsw)) ? 4 : 0;
this.palette[index] = RGB8[r | g | b];
}
setColorLatch(a: number, v: number): void {
this.color_latch[a & 3] = v;
this.updatePalette();
}
drawSprite(xpos: number, ypos: number, ofs: number, palind: number) {
var sx = 236 - xpos - 4;
var sy = 244 - ypos - 4;
/*
m_gfxdecode->gfx(0)->transpen(bitmap,cliprect,
((*m_spriteno >> 4) & 0x0f) + 32 + 16 * sprite_set_2, 1,
0, 0, sx, sy, 0);*/
sy += 15;
sy -= this.scanline;
if (sy >= 0 && sy < 16) {
sy = 15 - sy;
//console.log("draw sprite", sx, sy, ofs);
let yofs = this.scanline * this.canvasWidth;
let pix = this.sprite_gfx[ofs + sy];
for (let x = 0; x < 8; x++) {
if (pix & (0x80 >> x)) {
this.pixels[yofs + sx + x] = this.palette[palind];
}
}
pix = this.sprite_gfx[ofs + sy + 16];
for (let x = 0; x < 8; x++) {
if (pix & (0x80 >> x)) {
this.pixels[yofs + sx + x + 8] = this.palette[palind];
}
}
}
}
drawSprite1() {
let xpos = this.ram[0x5000];
let ypos = this.ram[0x5040];
let set = (this.ram[0x5101] & 0x20) ? 1 : 0;
let sprite = (this.ram[0x5100] & 0x0f) + 16 * set;
this.drawSprite(xpos, ypos, sprite * 32, 1);
}
drawSprite2() {
let xpos = this.ram[0x5080];
let ypos = this.ram[0x50c0];
let set = (this.ram[0x5101] & 0x40) ? 1 : 0;
let sprite = (this.ram[0x5100] >> 4) + 16 * set;
this.drawSprite(xpos, ypos, sprite * 32, 3);
}
startScanline(): void {
}
drawScanline(): void {
const y = this.scanline;
const row = y >> 3;
const yofs = y * this.canvasWidth;
for (let x = 0; x < 256; x++) {
const col = x >> 3;
let code = this.ram[this.scrnbase + row * 32 + col];
let color1 = 4 + ((code >> 6) & 0x03);
let pix = this.ram[this.charbase + code * 8 + (y & 7)];
let palind = (pix & (0x80 >> (x & 7))) ? color1 : 0;
this.pixels[yofs + x] = this.palette[palind];
}
this.drawSprite2();
this.drawSprite1();
}
postFrame() {
this.inputs[3] &= 0x7f; // TODO?
this.cpu.IRQ();
}
getVideoParams() {
return { width: 256, height: 256, aspect: 6/5 };
}
}
const RGB8 = [
0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff,
0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
];
/*
0000-00FF R/W Zero Page RAM
0100-01FF R/W Stack RAM
0200-03FF R/W Scratchpad RAM
0800-3FFF R Program ROM (Targ, Spectar only)
1A00 R PX3 (Player 2 inputs) (Fax only)
bit 4 D
bit 5 C
bit 6 B
bit 7 A
1C00 R PX2 (Player 1 inputs) (Fax only)
bit 0 2 player start
bit 1 1 player start
bit 4 D
bit 5 C
bit 6 B
bit 7 A
2000-3FFF R Banked question ROM (Fax only)
4000-43FF R/W Screen RAM
4800-4FFF R/W Character Generator RAM (except Pepper II and Fax)
5000 W Motion Object 1 Horizontal Position Latch (sprite 1 X)
5040 W Motion Object 1 Vertical Position Latch (sprite 1 Y)
5080 W Motion Object 2 Horizontal Position Latch (sprite 2 X)
50C0 W Motion Object 2 Vertical Position Latch (sprite 2 Y)
5100 R Option Dipswitch Port
bit 0 coin 2 (NOT inverted) (must activate together with $5103 bit 5)
bit 1-2 bonus
bit 3-4 coins per play
bit 5-6 lives
bit 7 US/UK coins
5100 W Motion Objects Image Latch
Sprite number bits 0-3 Sprite #1 4-7 Sprite #2
5101 R Control Inputs Port
bit 0 start 1
bit 1 start 2
bit 2 right
bit 3 left
bit 5 up
bit 6 down
bit 7 coin 1 (must activate together with $5103 bit 6)
5101 W Output Control Latch (not used in PEPPER II upright)
bit 7 Enable sprite #1
bit 6 Enable sprite #2
5103 R Interrupt Condition Latch
bit 0 LNG0 - supposedly a language DIP switch
bit 1 LNG1 - supposedly a language DIP switch
bit 2 different for each game, but generally a collision bit
bit 3 TABLE - supposedly a cocktail table DIP switch
bit 4 different for each game, but generally a collision bit
bit 5 coin 2 (must activate together with $5100 bit 0)
bit 6 coin 1 (must activate together with $5101 bit 7)
bit 7 L256 - VBlank?
5213 R IN2 (Mouse Trap)
bit 3 blue button
bit 2 free play
bit 1 red button
bit 0 yellow button
52XX R/W Audio/Color Board Communications
6000-6FFF R/W Character Generator RAM (Pepper II, Fax only)
8000-FFF9 R Program memory space
FFFA-FFFF R Interrupt and Reset Vectors
Exidy Sound Board:
0000-07FF R/W RAM (mirrored every 0x7f)
0800-0FFF R/W 6532 Timer
1000-17FF R/W 6520 PIA
1800-1FFF R/W 8253 Timer
2000-27FF bit 0 Channel 1 Filter 1 enable
bit 1 Channel 1 Filter 2 enable
bit 2 Channel 2 Filter 1 enable
bit 3 Channel 2 Filter 2 enable
bit 4 Channel 3 Filter 1 enable
bit 5 Channel 3 Filter 2 enable
2800-2FFF 6840 Timer
3000 Bit 0..1 Noise select
3001 Bit 0..2 Channel 1 Amplitude
3002 Bit 0..2 Channel 2 Amplitude
3003 Bit 0..2 Channel 3 Amplitude
5800-7FFF ROM
Targ:
5200 Sound board control
bit 0 Music
bit 1 Shoot
bit 2 unused
bit 3 Swarn
bit 4 Sspec
bit 5 crash
bit 6 long
bit 7 game
5201 Sound board control
bit 0 note
bit 1 upper
MouseTrap:
5101 W MouseTrap P1/P2 LED States
bit 2 Player 1 LED state
bit 4 Player 2 LED state
MouseTrap Digital Sound:
0000-3FFF ROM
IO:
A7 = 0: R Communication from sound processor
A6 = 0: R CVSD Clock State
A5 = 0: W Busy to sound processor
A4 = 0: W Data to CVSD
*/

View File

@ -26,6 +26,12 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
loadBIOS(srcArray: Uint8Array) {
super.loadBIOS(srcArray);
}
async fetchBIOS() {
let bios = new Uint8Array(20480);
bios.set(DEFAULT_BIOS, bios.length - DEFAULT_BIOS.length);
this.allocateBIOS(bios);
this.loadBIOS(new Uint8Array(bios));
}
reset() {
super.reset();
// clear keyboard
@ -93,6 +99,12 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
getRasterY() {
return this.exports.machine_get_raster_line(this.sys);
}
getRasterCanvasPosition() {
return {
x: -1, // TODO?
y: this.getRasterY() - 14,
}
}
getCPUState() {
this.exports.machine_save_cpu_state(this.sys, this.cpustateptr);
var s = this.cpustatearr;
@ -164,3 +176,19 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
}
}
// pretty much just runs autostart ROM and not much else...
const DEFAULT_BIOS = [
0xA2, 0x10, 0xA0, 0x91, 0x60, 0x71, 0xFF, 0x71, 0xFF, 0x5C, 0xFF, 0xA2, 0xFF, 0x78, 0x9A, 0xD8,
0x6C, 0x00, 0xA0, 0xA2, 0x45, 0xA0, 0xFF, 0x18, 0x78, 0x6C, 0x18, 0x03, 0x48, 0x8A, 0x48, 0x98,
0x48, 0xAD, 0x1D, 0x91, 0x10, 0x03, 0x6C, 0x02, 0xA0, 0x4C, 0x6C, 0xFF, 0x68, 0xA8, 0x68, 0xAA,
0x68, 0x40, 0x48, 0x8A, 0x48, 0x98, 0x48, 0xBA, 0xBD, 0x04, 0x01, 0x29, 0x10, 0xF0, 0x03, 0x6C,
0x16, 0x03, 0x6C, 0x14, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4C, 0x53, 0xFF, 0x4C, 0x44, 0xFF,
0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C,
0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44,
0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF,
0x6C, 0x1A, 0x03, 0x6C, 0x1C, 0x03, 0x6C, 0x1E, 0x03, 0x6C, 0x20, 0x03, 0x6C, 0x22, 0x03, 0x6C,
0x24, 0x03, 0x6C, 0x26, 0x03, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF, 0x4C, 0x44,
0xFF, 0x6C, 0x28, 0x03, 0x6C, 0x2A, 0x03, 0x6C, 0x2C, 0x03, 0x4C, 0x44, 0xFF, 0x4C, 0x44, 0xFF,
0x4C, 0x44, 0xFF, 0x4C, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0xFF, 0x4B, 0xFF, 0x72, 0xFF
];

View File

@ -12,6 +12,7 @@ export function importPlatform(name: string) : Promise<any> {
case "coleco": return import("../platform/coleco");
case "cpc": return import("../platform/cpc");
case "devel": return import("../platform/devel");
case "exidy": return import("../platform/exidy");
case "galaxian": return import("../platform/galaxian");
case "kim1": return import("../platform/kim1");
case "markdown": return import("../platform/markdown");

View File

@ -1,13 +1,13 @@
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../common/baseplatform";
import { Platform, Preset, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { AppleII } from "../machine/apple2";
import { Base6502MachinePlatform } from "../common/baseplatform";
import { CodeAnalyzer_apple2 } from "../common/analysis";
import { BaseMAME6502Platform } from "../common/mameplatform";
const APPLE2_PRESETS = [
{id:'sieve.c', name:'Sieve'},
const APPLE2_PRESETS : Preset[] = [
{id:'sieve.c', name:'Sieve', category:"C"},
{id:'keyboardtest.c', name:'Keyboard Test'},
{id:'mandel.c', name:'Mandelbrot'},
{id:'tgidemo.c', name:'TGI Graphics Demo'},
@ -16,12 +16,12 @@ const APPLE2_PRESETS = [
{id:'cosmic.c', name:'Cosmic Impalas'},
{id:'farmhouse.c', name:"Farmhouse Adventure"},
{id:'yum.c', name:"Yum Dice Game"},
{id:'lzgtest.c', name:"LZG Decompressor (C)"},
{id:'hgrtest.a', name:"HGR Test (ASM)"},
{id:'conway.a', name:"Conway's Game of Life (ASM)"},
{id:'lz4fh.a', name:"LZ4FH Decompressor (ASM)"},
{id:'deltamod.dasm', name:"Delta Modulation (ASM)"},
// {id:'zap.dasm', name:"ZAP! (ASM)"},
{id:'lz4test.c', name:"LZ4 Decompressor"},
{id:'hgrtest.a', name:"HGR Test", category:"Assembly Language"},
{id:'conway.a', name:"Conway's Game of Life"},
{id:'lz4fh.a', name:"LZ4FH Decompressor"},
{id:'deltamod.dasm', name:"Delta Modulation Audio"},
// {id:'zap.dasm', name:"ZAP!"},
// {id:'tb_6502.s', name:'Tom Bombem (assembler game)'},
];
@ -77,7 +77,7 @@ class NewApple2Platform extends Base6502MachinePlatform<AppleII> implements Plat
{name:'Hires Page 2',start:0x4000,size:0x2000,type:'ram'},
{name:'RAM',start:0x6000,size:0x6000,type:'ram'},
{name:'I/O',start:0xc000,size:0x1000,type:'io'},
{name:'ROM',start:0xd000,size:0x3000-6,type:'rom'},
{name:'ROM',start:0xd000,size:0x3000,type:'rom'},
] } };
getROMExtension(rom:Uint8Array) {
if (rom && rom.length == 35*16*256) return ".dsk"; // DSK image

View File

@ -1,40 +1,49 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { BaseDebugPlatform, CpuState, EmuState, Platform, DisasmLine, Debuggable, Machine, BaseMachinePlatform } from "../common/baseplatform";
import { AnimationTimer, EmuHalt, padBytes, PLATFORMS, RasterVideo } from "../common/emu";
import { hex, lpad, loadScript } from "../common/util";
import { ARM32CPU } from "../common/cpu/ARM";
import { Platform, DisasmLine, Machine, BaseMachinePlatform } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { loadScript } from "../common/util";
import { ARM32Machine } from "../machine/arm32";
declare var uc, cs : any; // Unicorn module
declare var cs : any; // Unicorn module
const ARM32_PRESETS = [
{ id: 'vidfill.vasm', name: 'Video Memory Fill' },
{ id: 'vidfill.c', name: 'Video Memory Fill' },
];
const SCREEN_WIDTH = 160;
const SCREEN_HEIGHT = 128;
const ROM_START_ADDR = 0x0;
const HIROM_START_ADDR = 0xff800000;
const ROM_SIZE = 512*1024;
const RAM_START_ADDR = 0x20000000;
const RAM_SIZE = 512*1024;
const CLOCKS_PER_FRAME = 10000;
interface ARM32State extends EmuState {
r: Uint32Array; // registers
}
export abstract class BaseARMMachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
//getOpcodeMetadata = getOpcodeMetadata_z80;
getToolForFilename(fn: string) {
fn = fn.toLowerCase();
if (fn.endsWith('.vasm')) return "vasmarm";
else if (fn.endsWith('.armips')) return "armips";
else return "vasmarm";
if (fn.endsWith('.armips')) return "armips";
if (fn.endsWith('.c')) return "armtcc";
if (fn.endsWith('.s')) return "armtcc";
return "armtcc";
}
getPresets() { return ARM32_PRESETS; }
getDefaultExtension() { return ".vasm"; };
getDefaultExtension() { return ".c"; };
}
class ARM32Platform extends BaseARMMachinePlatform<ARM32Machine> implements Platform {
@ -53,10 +62,16 @@ class ARM32Platform extends BaseARMMachinePlatform<ARM32Machine> implements Plat
newMachine() { return new ARM32Machine(); }
readAddress(a) { return this.machine.read(a); }
getMemoryMap = function() { return { main:[
{name:'ROM',start:0x0000000,size:0x80000,type:'rom'},
{name:'RAM',start:0x2000000,size:0x80000,type:'ram'},
{name:'ROM',start:0x0000000,size:0x100000,type:'ram'},
{name:'I/O',start:0x4000000,size:0x100,type:'io'},
] } };
getPlatformName() { return "ARM7"; }
getDebugTree() {
return {
...this.machine.cpu.getDebugTree(),
dwarf: this.debugSymbols.debuginfo
}
}
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
var is_thumb = this.machine.cpu.isThumb();
var capstone = is_thumb ? this.capstone_thumb : this.capstone_arm;

View File

@ -26,6 +26,10 @@ const ASTROCADE_BIOS_PRESETS = [
{ id: 'bios.c', name: 'BIOS' },
];
const ASTROCADE_ARCADE_PRESETS = [
{ id: 'hello.c', name: 'Hello Graphics' },
];
class BallyAstrocadePlatform extends BaseZ80MachinePlatform<BallyAstrocade> implements Platform {
newMachine() { return new BallyAstrocade(false); }
@ -52,10 +56,13 @@ class BallyAstrocadeBIOSPlatform extends BallyAstrocadePlatform implements Platf
class BallyArcadePlatform extends BallyAstrocadePlatform implements Platform {
newMachine() { return new BallyAstrocade(true); }
getPresets() { return ASTROCADE_ARCADE_PRESETS; }
getMemoryMap = function() { return { main:[
{name:'ROM',start:0x0,size:0x4000,type:'rom'},
{name:'Magic RAM',start:0x0,size:0x4000,type:'ram'},
{name:'Screen RAM',start:0x4000,size:0x4000,type:'ram'},
{name:'ROM',start:0x8000,size:0x4000,type:'rom'},
] } };
}

View File

@ -1,13 +1,18 @@
import { Atari7800 } from "../machine/atari7800";
import { Platform, Base6502MachinePlatform } from "../common/baseplatform";
import { Platform, Base6502MachinePlatform, getToolForFilename_6502 } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
var Atari7800_PRESETS = [
{id:'sprites.dasm', name:'Sprites (ASM)'},
{id:'wsync.c', name:'WSYNC'},
{id:'sprites.dasm', name:'Sprites (ASM)', category:'Assembler'},
{id:'wsync.c', name:'WSYNC', category:'CC65'},
{id:'sprites.c', name:'Double Buffering'},
{id:'scroll.c', name:'Scrolling'},
{id:'test_conio.c78', name:'Conio Test', category:'cc7800'},
{id:'example_small_sprites.c78', name:'Small Sprites'},
{id:'example_vertical_scrolling.c78', name:'Vertical Scrolling'},
];
class Atari7800Platform extends Base6502MachinePlatform<Atari7800> implements Platform {
@ -24,7 +29,8 @@ class Atari7800Platform extends Base6502MachinePlatform<Atari7800> implements Pl
{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'},
{name:'Cartridge ROM',start:0x4000,size:0xc000-6,type:'rom'},
{name:'CPU Vectors',start:0xfffa,size:0x6,type:'rom'},
] } };
getROMExtension() { return ".a78"; }
getDebugTree() {
@ -32,6 +38,11 @@ class Atari7800Platform extends Base6502MachinePlatform<Atari7800> implements Pl
tree['display_list'] = this.machine.getDebugDisplayLists();
return tree;
}
getToolForFilename(filename: string) {
if (filename.endsWith(".cc7800")) return "cc7800";
if (filename.endsWith(".c78")) return "cc7800";
return getToolForFilename_6502(filename);
}
}
///

View File

@ -1,12 +1,12 @@
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502, Base6502MachinePlatform } from "../common/baseplatform";
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502, Base6502MachinePlatform, Preset } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { BaseMAME6502Platform } from "../common/mameplatform";
import { Atari5200, Atari800 } from "../machine/atari8";
declare var jt; // for 6502
var Atari8_PRESETS = [
var Atari8_PRESETS : Preset[] = [
{id:'hello.dasm', name:'Hello World (ASM)'},
{id:'hellopm.dasm', name:'Hello Sprites (ASM)'},
{id:'helloconio.c', name:'Text Mode (C)'},
@ -16,7 +16,7 @@ var Atari8_PRESETS = [
var Atari800_PRESETS = Atari8_PRESETS.concat([
{id:'testmusic.c', name:'POKEY Music (C)'},
{id:'sieve.bas', name:'Benchmark (FastBasic)'},
{id:'sieve.bas', name:'Benchmark (FastBasic)', category:'FastBasic'},
{id:'pmtest.bas', name:'Sprites Test (FastBasic)'},
{id:'dli.bas', name:'DLI Test (FastBasic)'},
{id:'joyas.bas', name:'Match-3 Game (FastBasic)'},

View File

@ -1,27 +1,27 @@
import { C64_WASMMachine } from "../machine/c64";
import { Platform, Base6502MachinePlatform, getToolForFilename_6502, getOpcodeMetadata_6502 } from "../common/baseplatform";
import { Platform, Base6502MachinePlatform, getToolForFilename_6502, getOpcodeMetadata_6502, Preset } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { BaseMAME6502Platform } from "../common/mameplatform";
const C64_PRESETS = [
{id:'hello.dasm', name:'Hello World (ASM)'},
{id:'23matches.c', name:'23 Matches'},
{id:'tgidemo.c', name:'TGI Graphics Demo'},
{id:'upandaway.c', name:'Up, Up and Away'},
{id:'siegegame.c', name:'Siege Game'},
const C64_PRESETS : Preset[] = [
{id:'helloc.c', name:'Hello World', category:'C'},
{id:'screen_ram.c', name:'Screen RAM'},
{id:'joymove.c', name:'Sprite Movement'},
{id:'sprite_collision.c', name:'Sprite Collision'},
{id:'scroll1.c', name:'Scrolling (Single Buffer)'},
{id:'test_setirq.c', name:'Raster Interrupts'},
{id:'test_display_list.c', name:'Raster IRQ Library'},
{id:'scrolling_text.c', name:'Big Scrolling Text'},
{id:'side_scroller.c', name:'Side-Scrolling Game'},
{id:'scroll2.c', name:'Scrolling (Double Buffer)'},
{id:'scroll3.c', name:'Scrolling (Multidirectional)'},
{id:'scroll4.c', name:'Scrolling (Color RAM Buffering)'},
{id:'scroll5.c', name:'Scrolling (Camera Following)'},
{id:'side_scroller.c', name:'Side-Scrolling Game'},
{id:'scrollingmap1.c', name:'Scrolling Tile Map'},
{id:'fullscrollgame.c', name:'Full-Scrolling Game'},
{id:'test_multiplex.c', name:'Sprite Retriggering'},
{id:'test_multispritelib.c', name:'Sprite Multiplexing Library'},
{id:'scrolling_text.c', name:'Big Scrolling Text'},
{id:'mcbitmap.c', name:'Multicolor Bitmap Mode'},
{id:'testlz4.c', name:'LZ4 Bitmap Compression'},
//{id:'mandel.c', name:'Mandelbrot Fractal'},
@ -29,6 +29,15 @@ const C64_PRESETS = [
//{id:'sidtune.dasm', name:'Tiny SID Tune (ASM)'},
{id:'siddemo.c', name:'SID Player Demo'},
{id:'climber.c', name:'Climber Game'},
{id:'test_border_sprites.c', name:'Sprites in the Borders'},
{id:'sprite_stretch.c', name:'Sprite Stretching'},
{id:'plasma.c', name:'Plasma Demo'},
{id:'siegegame.c', name:'Siege Game'},
{id:'23matches.c', name:'23 Matches'},
{id:'tgidemo.c', name:'TGI Graphics Demo'},
{id:'upandaway.c', name:'Up, Up and Away'},
{id:'hello.dasm', name:'Hello World (DASM)', category:'Assembly Language'},
{id:'hello.dasm', name:'Hello World (ACME)'},
{id:'hello.wiz', name:'Hello Wiz (Wiz)'},
];

32
src/platform/exidy.ts Normal file
View File

@ -0,0 +1,32 @@
import { Base6502MachinePlatform, Platform } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { ExidyUGBv2 } from "../machine/exidy";
var EXIDY_PRESETS = [
{ id: 'minimal.c', name: 'Minimal Example', category: "C" },
];
class ExidyUGBPlatform extends Base6502MachinePlatform<ExidyUGBv2> implements Platform {
newMachine() { return new ExidyUGBv2(); }
getPresets() { return EXIDY_PRESETS; }
getDefaultExtension() { return ".dasm"; };
readAddress(a) { return this.machine.readConst(a); }
getMemoryMap() {
return { main: [
{ name: 'RAM', start: 0x00, size: 0x400, type: 'ram' },
{ name: 'Sprite I/O', start: 0x5000, size: 0x100, type: 'io' },
{ name: 'I/O', start: 0x5100, size: 0x3, type: 'io' },
{ name: 'PIA 6821', start: 0x5200, size: 0xf, type: 'io' },
{ name: 'Color Latches', start: 0x5210, size: 0x3, type: 'io' },
{ name: 'Screen RAM', start: 0x4000, size: 0x400, type: 'ram' },
{ name: 'Character RAM', start: 0x6800, size: 0x800, type: 'ram' },
{ name: 'Audio ROM', start: 0x5800, size: 0x2800, type: 'rom' },
{ name: 'Program ROM', start: 0x8000, size: 0x8000, type: 'rom' },
]
} }
}
PLATFORMS["exidy"] = ExidyUGBPlatform;

View File

@ -1,5 +1,5 @@
import { Platform, Base6502Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../common/baseplatform";
import { Platform, Base6502Platform, getOpcodeMetadata_6502, getToolForFilename_6502, Preset } from "../common/baseplatform";
import { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, KeyFlags, EmuHalt, ControllerPoller } from "../common/emu";
import { hex, byteArrayToString } from "../common/util";
import { CodeAnalyzer_nes } from "../common/analysis";
@ -10,7 +10,7 @@ import Mousetrap = require('mousetrap');
import jsnes = require('../../jsnes');
import { BaseMAME6502Platform } from "../common/mameplatform";
const JSNES_PRESETS = [
const JSNES_PRESETS : Preset[] = [
{id:'hello.c', name:'Hello World'},
{id:'attributes.c', name:'Attribute Table'},
{id:'scroll.c', name:'Scrolling'},
@ -32,16 +32,16 @@ const JSNES_PRESETS = [
{id:'climber.c', name:'Climber Game'},
{id:'bankswitch.c', name:'Bank Switching'},
{id:'irq.c', name:'IRQ Scanline Counter'},
{id:'ex0.dasm', name:'Initialization (ASM)'},
{id:'ex1.dasm', name:'Hello World (ASM)'},
{id:'ex2.dasm', name:'Scrolling Demo (ASM)'},
{id:'ex3.dasm', name:'Sprite Demo (ASM)'},
{id:'ex4.dasm', name:'Controller Demo (ASM)'},
{id:'musicdemo.dasm', name:'Famitone Demo (ASM)'},
{id:'xyscroll.dasm', name:'XY Split Scrolling (ASM)'},
// {id:'scrollrt.dasm', name:'Line-by-line Scrolling (ASM)'},
{id:'road.dasm', name:'3-D Road Demo (ASM)'},
{id:'chase/game.c', name:'Shiru\'s Chase Game'},
{id:'ex0.dasm', name:'Initialization', category:'Assembly Language (ASM)'},
{id:'ex1.dasm', name:'Hello World'},
{id:'ex2.dasm', name:'Scrolling Demo'},
{id:'ex3.dasm', name:'Sprite Demo'},
{id:'ex4.dasm', name:'Controller Demo'},
{id:'musicdemo.dasm', name:'Famitone Demo'},
{id:'xyscroll.dasm', name:'XY Split Scrolling'},
// {id:'scrollrt.dasm', name:'Line-by-line Scrolling'},
{id:'road.dasm', name:'3-D Road Demo'},
{id:'chase/game.c', name:'Shiru\'s Chase Game', category:'Other'},
{id:'hello.wiz', name:'Hello (Wiz)'},
];

View File

@ -1,6 +1,6 @@
import { Platform, BasePlatform, cpuStateToLongString_6502, dumpStackToString, DisasmLine, CpuState, getToolForFilename_6502 } from "../common/baseplatform";
import { PLATFORMS, dumpRAM, EmuHalt, RasterVideo, __createCanvas } from "../common/emu";
import { Platform, BasePlatform, cpuStateToLongString_6502, dumpStackToString, DisasmLine, CpuState, Preset } from "../common/baseplatform";
import { PLATFORMS, dumpRAM, EmuHalt, __createCanvas, drawCrosshair } from "../common/emu";
import { hex, loadScript, lpad, tobin } from "../common/util";
import { CodeAnalyzer_vcs } from "../common/analysis";
import { disassemble6502 } from "../common/cpu/disasm6502";
@ -11,7 +11,7 @@ import { BaseMAME6502Platform } from "../common/mameplatform";
declare var Javatari : any;
declare var jt : any; // 6502
const VCS_PRESETS = [
const VCS_PRESETS : Preset[] = [
{id:'examples/hello.a', chapter:4, name:'Hello 6502 and TIA'},
{id:'examples/vsync.a', chapter:5, name:'Painting on the CRT', title:'Color Bars'},
{id:'examples/playfield.a', chapter:6, name:'Playfield Graphics'},
@ -42,11 +42,11 @@ const VCS_PRESETS = [
{id:'examples/road.a', chapter:33, name:'Pseudo 3D Road'},
{id:'examples/bankswitching.a', chapter:35, name:'Bankswitching'},
{id:'examples/wavetable.a', chapter:36, name:'Wavetable Sound'},
{id:'examples/fracpitch.a', name:'Fractional Pitch'},
{id:'examples/pal.a', name:'PAL Video Output'},
// {id:'examples/testlibrary.a', name:'VCS Library Demo'},
// {id:'examples/music2.a', name:'Pitch-Accurate Music'},
// {id:'examples/fullgame.a', name:'Thru Hike: The Game', title:'Thru Hike'},
{id:'examples/fracpitch.a', name:'Fractional Pitch', category:'BASIC and Other Languages'},
{id:'bb/helloworld.bas', name:'Hello World (batariBASIC)'},
{id:'bb/draw.bas', name:'Playfield Draw (batariBASIC)'},
{id:'bb/sample.bas', name:'Sprite Test (batariBASIC)'},
@ -62,6 +62,7 @@ function getToolForFilename_vcs(fn: string) {
if (fn.endsWith(".wiz")) return "wiz";
if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic";
if (fn.endsWith(".ca65")) return "ca65";
if (fn.endsWith(".acme")) return "acme";
//if (fn.endsWith(".inc")) return "ca65";
if (fn.endsWith(".c")) return "cc65";
//if (fn.endsWith(".h")) return "cc65";
@ -72,6 +73,7 @@ function getToolForFilename_vcs(fn: string) {
class VCSPlatform extends BasePlatform {
lastBreakState; // last breakpoint state
canvas : HTMLCanvasElement;
// TODO: super hack for ProbeBitmap view
machine = {
@ -92,10 +94,10 @@ class VCSPlatform extends BasePlatform {
// show console div and start
$("#javatari-div").show();
Javatari.start();
var console = Javatari.room.console;
var jaconsole = Javatari.room.console;
// intercept clockPulse function
console.oldClockPulse = console.clockPulse;
console.clockPulse = function() {
jaconsole.oldClockPulse = jaconsole.clockPulse;
jaconsole.clockPulse = function() {
self.updateRecorder();
self.probe.logNewFrame();
this.oldClockPulse();
@ -106,18 +108,19 @@ class VCSPlatform extends BasePlatform {
}
}
// intercept TIA end of line
var videoSignal = console.tia.getVideoOutput();
var videoSignal = jaconsole.tia.getVideoOutput();
videoSignal.oldNextLine = videoSignal.nextLine;
videoSignal.nextLine = function(pixels, vsync) {
self.probe.logNewScanline();
return this.oldNextLine(pixels, vsync);
}
// resize after added to dom tree
var jacanvas = $("#javatari-screen").find("canvas");
var jacanvas = $("#javatari-screen").find("canvas")[0];
const resizeObserver = new ResizeObserver(entries => {
this.resize();
});
resizeObserver.observe(jacanvas[0]);
resizeObserver.observe(jacanvas);
this.canvas = jacanvas;
}
loadROM(title, data) {
@ -148,6 +151,16 @@ class VCSPlatform extends BasePlatform {
getRasterLineClock() : number {
return this.getRasterPosition().x;
}
getRasterCanvasPosition() : {x:number,y:number} {
let p = Javatari.room.console.tia.getVideoOutput().monitor.getDisplayParameters();
let {x,y} = this.getRasterPosition();
let canvasPos = {
x: (x - p.displayOriginX) * p.displayWidth * p.displayScaleX / (p.signalWidth - p.displayOriginX),
y: (y - p.displayOriginY) * p.displayHeight * p.displayScaleY / p.displayHeight
};
console.log(x,y,canvasPos,p);
return canvasPos;
}
// TODO: Clock changes this on event, so it may not be current
isRunning() {
@ -194,6 +207,8 @@ class VCSPlatform extends BasePlatform {
Javatari.room.speaker.mute();
this.lastBreakState = state;
callback(state);
// TODO: we have to delay because javatari timer is still running
setTimeout(() => this.updateVideoDebugger(), 100);
}
Javatari.room.speaker.mute();
}
@ -248,6 +263,7 @@ class VCSPlatform extends BasePlatform {
}
readAddress(addr) {
// TODO: shouldn't have to do this when debugging
// TODO: don't read bank switch addresses
if (this.lastBreakState && addr >= 0x80 && addr < 0x100)
return this.getRAMForState(this.lastBreakState)[addr & 0x7f];
else if ((addr & 0x1280) === 0x280)
@ -347,7 +363,8 @@ class VCSPlatform extends BasePlatform {
{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'},
{name:'Cartridge ROM',start:0xf000,size:0x1000-6,type:'rom'},
{name:'CPU Vectors',start:0xfffa,size:0x6,type:'rom'},
]}};
// probing
@ -419,6 +436,15 @@ class VCSPlatform extends BasePlatform {
var xt = (1 - scale) * 50;
$('#javatari-div').css('transform', `translateX(-${xt}%) translateY(-${xt}%) scale(${scale})`);
}
updateVideoDebugger() {
const {x,y} = this.getRasterCanvasPosition();
if (x >= 0 || y >= 0) {
const ctx = this.canvas?.getContext('2d');
if (ctx) {
drawCrosshair(ctx, x, y, 2);
}
}
}
};
// TODO: mixin for Base6502Platform?

View File

@ -35,7 +35,8 @@ var VERILOG_PRESETS = [
{id:'sound_generator.v', name:'Sound Generator'},
{id:'lfsr.v', name:'Linear Feedback Shift Register'},
{id:'starfield.v', name:'Scrolling Starfield'},
{id:'alu.v', name:'ALU'},
{id:'alu.v', name:'ALU', category:'CPU'},
{id:'cpu8.v', name:'Simple 8-Bit CPU'},
{id:'racing_game_cpu.v', name:'Racing Game with CPU'},
{id:'framebuffer.v', name:'Frame Buffer'},
@ -45,9 +46,10 @@ var VERILOG_PRESETS = [
{id:'cpu_platform.v', name:'CPU Platform'},
{id:'test2.asm', name:'16-bit ASM Game'},
{id:'cpu6502.v', name:'6502 CPU'},
{id:'test_pattern.ice', name:'Test Pattern (Silice)'},
{id:'copperbars.ice', name:'Animated Bars (Silice)'},
{id:'rototexture.ice', name:'Rotating Texture (Silice)'},
{id:'test_pattern.ice', name:'Test Pattern', category:'Silice'},
{id:'copperbars.ice', name:'Animated Bars'},
{id:'rototexture.ice', name:'Rotating Texture'},
//{id:'life.ice', name:'Conway\'s Life (Silice)'},
];

View File

@ -5,9 +5,9 @@ import { PLATFORMS } from "../common/emu";
import { BaseMAME6502Platform } from "../common/mameplatform";
const VIC20_PRESETS = [
{id:'hello.dasm', name:'Hello World (ASM)'},
// {id:'hello.dasm', name:'Hello World (ASM)'},
{id:'hellocart.dasm', name:'Hello Cartridge (ASM)'},
{id:'siegegame.c', name:'Siege Game (C)'},
// {id:'siegegame.c', name:'Siege Game (C)'},
];
const VIC20_MEMORY_MAP = { main:[

63
src/test/testelfparser.ts Normal file
View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import assert from "assert";
import { DWARFParser, ELFParser } from "../common/binutils";
describe('test ELFParser', () => {
const fs = require('fs');
const data = fs.readFileSync('./test/exes/arm32.elf');
const elfParser = new ELFParser(new Uint8Array(data));
it('should parse sections and symbols', () => {
/*
elfParser.sectionHeaders.forEach((section, index) => {
console.log('section', index, section.name, section.type, section.vaddr.toString(16), section.size.toString(16));
});
elfParser.getSymbols().forEach((symbol, index) => {
console.log('symbol', index, symbol.info, symbol.other, symbol.name, symbol.value.toString(16));
});
*/
assert.strictEqual(21, elfParser.sectionHeaders.length);
assert.strictEqual(29, elfParser.getSymbols().length);
assert.ok(elfParser.sectionHeaders.find((section) => section.name === '.text') != null);
assert.ok(elfParser.getSymbols().find((symbol) => symbol.name === 'main') != null);
});
it('should parse DWARF info', () => {
const dwarf = new DWARFParser(elfParser);
assert.strictEqual(2, dwarf.units.length);
const cu = dwarf.units[0];
// TODO: check info content
const li = dwarf.lineInfos[0];
assert.strictEqual('crt0.c', li.files[1].name);
/*
assert.ok(info != null);
assert.ok(info!.lineNumberProgram != null);
assert.ok(info!.lineNumberProgram!.length > 0);
assert.ok(info!.lineNumberProgram![0].file != null);
assert.ok(info!.lineNumberProgram![0].file!.name != null);
assert.ok(info!.lineNumberProgram![0].file!.name!.length > 0);
*/
});
});

24
src/test/testlibretro.ts Normal file
View File

@ -0,0 +1,24 @@
import assert from "assert";
import * as fs from "fs";
import { LibRetroRunner } from "../common/wasi/libretro";
async function loadLibretro() {
const wasmdata = fs.readFileSync(`./wasi/stella2014_libretro_2.wasm`);
let shim = new LibRetroRunner();
await shim.loadAsync(wasmdata);
return shim;
}
/*
describe('test WASI libretro', function () {
it('libretro init', async function () {
let shim = await loadLibretro();
assert.strictEqual(1, shim.retro_api_version());
shim.retro_init();
let romdata = fs.readFileSync(`./test/roms/vcs/brickgame.rom`);
shim.load_rom('brickgame.rom', romdata);
shim.reset();
shim.advance();
});
});
*/

80
src/test/testwasishim.ts Normal file
View File

@ -0,0 +1,80 @@
import assert from "assert";
import { WASIRunner } from "../common/wasi/wasishim";
import * as fs from "fs";
async function loadWASM(filename: string) {
const wasmdata = fs.readFileSync(`./src/worker/wasm/${filename}.wasm`);
let shim = new WASIRunner();
await shim.loadAsync(wasmdata);
return shim;
}
async function loadDASM() {
return loadWASM('dasm-wasisdk');
}
async function loadCC7800() {
return loadWASM('cc7800');
}
describe('test WASI DASM', function () {
it('dasm help', async function () {
let shim = await loadDASM();
let errno = shim.run();
assert.strictEqual(errno, 1);
});
it('dasm file not found', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "file_not_found.asm"]);
let errno = shim.run();
assert.strictEqual(errno, 2);
});
it('dasm file not found 2', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "/file.asm", "-d"]);
let errno = shim.run();
assert.strictEqual(errno, 2);
});
it('dasm bad args 1', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "file_not_found.asm", "extra_arg.asm"]);
let errno = shim.run();
assert.strictEqual(errno, 1);
});
it('dasm bad args 2', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "file_not_found.asm", "-E9"]);
let errno = shim.run();
assert.strictEqual(errno, 1);
});
it('dasm empty file', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "empty.asm"]);
shim.addPreopenDirectory("/root");
shim.fs.putFile("/root/empty.asm", "");
let errno = shim.run();
assert.strictEqual(errno, 0);
});
it('dasm small file', async function () {
let shim = await loadDASM();
shim.setArgs(["dasm", "empty.asm"]);
shim.addPreopenDirectory("/root");
shim.fs.putFile("/root/empty.asm", " processor 6502\n org $100\n nop");
let errno = shim.run();
assert.strictEqual(errno, 0);
let aout = shim.fs.getFile("/root/a.out");
assert.deepStrictEqual(Array.from(aout.getBytes()), [0x00, 0x01, 0xea]);
});
});
describe('test WASI cc7800', function () {
it('cc7800 help', async function () {
let shim = await loadCC7800();
shim.setArgs(["cc7800", '-h']);
let errno = shim.run();
assert.strictEqual(errno, 0);
const stdout = shim.fds[1].getBytesAsString();
console.log(stdout);
assert.ok(stdout.indexOf('Usage: cc7800') >= 0);
});
});

433
src/worker/builder.ts Normal file
View File

@ -0,0 +1,433 @@
import { convertDataToUint8Array, getBasePlatform } from "../common/util";
import { WorkerBuildStep, WorkerError, WorkerErrorResult, WorkerMessage, WorkerResult, WorkingStore } from "../common/workertypes";
import { PLATFORM_PARAMS } from "./platforms";
import { TOOLS } from "./workertools";
/// working file store and build steps
const PSRC = "../../src/";
export const PWORKER = PSRC + "worker/";
export type FileData = string | Uint8Array;
export type FileEntry = {
path: string
encoding: string
data: FileData
ts: number
};
export type BuildOptions = {
mainFilePath: string,
processFn?: (s: string, d: FileData) => FileData
};
// TODO
export type BuildStepResult = WorkerResult | WorkerNextToolResult;
export interface WorkerNextToolResult {
nexttool?: string
linktool?: string
path?: string
args: string[]
files: string[]
bblines?: boolean
}
export interface BuildStep extends WorkerBuildStep {
files?: string[]
args?: string[]
nextstep?: BuildStep
linkstep?: BuildStep
params?
result?: BuildStepResult
code?
prefix?
maxts?
debuginfo?
};
///
export class FileWorkingStore implements WorkingStore {
workfs: { [path: string]: FileEntry } = {};
workerseq: number = 0;
items: {};
constructor() {
this.reset();
}
reset() {
this.workfs = {};
this.newVersion();
}
currentVersion() {
return this.workerseq;
}
newVersion() {
let ts = new Date().getTime();
if (ts <= this.workerseq)
ts = ++this.workerseq;
return ts;
}
putFile(path: string, data: FileData): FileEntry {
var encoding = (typeof data === 'string') ? 'utf8' : 'binary';
var entry = this.workfs[path];
if (!entry || !compareData(entry.data, data) || entry.encoding != encoding) {
this.workfs[path] = entry = { path: path, data: data, encoding: encoding, ts: this.newVersion() };
console.log('+++', entry.path, entry.encoding, entry.data.length, entry.ts);
}
return entry;
}
hasFile(path: string) {
return this.workfs[path] != null;
}
getFileData(path: string): FileData {
return this.workfs[path] && this.workfs[path].data;
}
getFileAsString(path: string): string {
let data = this.getFileData(path);
if (data != null && typeof data !== 'string')
throw new Error(`${path}: expected string`)
return data as string; // TODO
}
getFileEntry(path: string): FileEntry {
return this.workfs[path];
}
setItem(key: string, value: object) {
this.items[key] = value;
}
}
export var store = new FileWorkingStore();
///
export function errorResult(msg: string): WorkerErrorResult {
return { errors: [{ line: 0, msg: msg }] };
}
export class Builder {
steps: BuildStep[] = [];
startseq: number = 0;
// returns true if file changed during this build step
wasChanged(entry: FileEntry): boolean {
return entry.ts > this.startseq;
}
async executeBuildSteps(): Promise<WorkerResult> {
this.startseq = store.currentVersion();
var linkstep: BuildStep = null;
while (this.steps.length) {
var step = this.steps.shift(); // get top of array
var platform = step.platform;
var [tool, remoteTool] = step.tool.split(':', 2);
var toolfn = TOOLS[tool];
if (!toolfn) {
throw Error(`no tool named "${tool}"`);
}
if (remoteTool) {
step.tool = remoteTool;
}
step.params = PLATFORM_PARAMS[getBasePlatform(platform)];
try {
step.result = await toolfn(step);
} catch (e) {
console.log("EXCEPTION", e, e.stack);
return errorResult(e + ""); // TODO: catch errors already generated?
}
if (step.result) {
(step.result as any).params = step.params; // TODO: type check
if (step.debuginfo) {
let r = step.result as any; // TODO
if (!r.debuginfo) r.debuginfo = {};
Object.assign(r.debuginfo, step.debuginfo);
}
// errors? return them
if ('errors' in step.result && step.result.errors.length) {
applyDefaultErrorPath(step.result.errors, step.path);
return step.result;
}
// if we got some output, return it immediately
if ('output' in step.result && step.result.output) {
return step.result;
}
// combine files with a link tool?
if ('linktool' in step.result) {
// add to existing link step
if (linkstep) {
linkstep.files = linkstep.files.concat(step.result.files);
linkstep.args = linkstep.args.concat(step.result.args);
} else {
linkstep = {
tool: step.result.linktool,
platform: platform,
files: step.result.files,
args: step.result.args
};
}
linkstep.debuginfo = step.debuginfo; // TODO: multiple debuginfos
}
// process with another tool?
if ('nexttool' in step.result) {
var asmstep: BuildStep = {
tool: step.result.nexttool,
platform: platform,
...step.result
}
this.steps.push(asmstep);
}
// process final step?
if (this.steps.length == 0 && linkstep) {
this.steps.push(linkstep);
linkstep = null;
}
}
}
}
async handleMessage(data: WorkerMessage): Promise<WorkerResult> {
this.steps = [];
// file updates
if (data.updates) {
data.updates.forEach((u) => store.putFile(u.path, u.data));
}
// object update
if (data.setitems) {
data.setitems.forEach((i) => store.setItem(i.key, i.value));
}
// build steps
if (data.buildsteps) {
this.steps.push.apply(this.steps, data.buildsteps);
}
// single-file
if (data.code) {
this.steps.push(data as BuildStep); // TODO: remove cast
}
// execute build steps
if (this.steps.length) {
var result = await this.executeBuildSteps();
return result ? result : { unchanged: true };
}
// TODO: cache results
// message not recognized
console.log("Unknown message", data);
}
}
function applyDefaultErrorPath(errors: WorkerError[], path: string) {
if (!path) return;
for (var i = 0; i < errors.length; i++) {
var err = errors[i];
if (!err.path && err.line) err.path = path;
}
}
function compareData(a: FileData, b: FileData): boolean {
if (a.length != b.length) return false;
if (typeof a === 'string' && typeof b === 'string') {
return a == b;
} else {
for (var i = 0; i < a.length; i++) {
//if (a[i] != b[i]) console.log('differ at byte',i,a[i],b[i]);
if (a[i] != b[i]) return false;
}
return true;
}
}
export const builder = new Builder();
var _t1;
export function starttime() { _t1 = new Date(); }
export function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); }
///
export function putWorkFile(path: string, data: FileData) {
return store.putFile(path, data);
}
export function getWorkFileAsString(path: string): string {
return store.getFileAsString(path);
}
export function populateEntry(fs, path: string, entry: FileEntry, options: BuildOptions) {
var data = entry.data;
if (options && options.processFn) {
data = options.processFn(path, data);
}
// create subfolders
var toks = path.split('/');
if (toks.length > 1) {
for (var i = 0; i < toks.length - 1; i++)
try {
fs.mkdir(toks[i]);
} catch (e) { }
}
// write file
fs.writeFile(path, data, { encoding: entry.encoding });
var time = new Date(entry.ts);
fs.utime(path, time, time);
console.log("<<<", path, entry.data.length);
}
// can call multiple times (from populateFiles)
export function gatherFiles(step: BuildStep, options?: BuildOptions): number {
var maxts = 0;
if (step.files) {
for (var i = 0; i < step.files.length; i++) {
var path = step.files[i];
var entry = store.workfs[path];
if (!entry) {
throw new Error("No entry for path '" + path + "'");
} else {
maxts = Math.max(maxts, entry.ts);
}
}
}
else if (step.code) {
var path = step.path ? step.path : options.mainFilePath; // TODO: what if options null
if (!path) throw Error("need path or mainFilePath");
var code = step.code;
var entry = putWorkFile(path, code);
step.path = path;
step.files = [path];
maxts = entry.ts;
}
else if (step.path) {
var path = step.path;
var entry = store.workfs[path];
maxts = entry.ts;
step.files = [path];
}
if (step.path && !step.prefix) {
step.prefix = getPrefix(step.path);
}
step.maxts = maxts;
return maxts;
}
export function getPrefix(s: string): string {
var pos = s.lastIndexOf('.');
return (pos > 0) ? s.substring(0, pos) : s;
}
export function populateFiles(step: BuildStep, fs, options?: BuildOptions) {
gatherFiles(step, options);
if (!step.files) throw Error("call gatherFiles() first");
for (var i = 0; i < step.files.length; i++) {
var path = step.files[i];
populateEntry(fs, path, store.workfs[path], options);
}
}
export function populateExtraFiles(step: BuildStep, fs, extrafiles) {
if (extrafiles) {
for (var i = 0; i < extrafiles.length; i++) {
var xfn = extrafiles[i];
// is this file cached?
if (store.workfs[xfn]) {
fs.writeFile(xfn, store.workfs[xfn].data, { encoding: 'binary' });
continue;
}
// fetch from network
var xpath = "lib/" + getBasePlatform(step.platform) + "/" + xfn;
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open("GET", PWORKER + xpath, false); // synchronous request
xhr.send(null);
if (xhr.response && xhr.status == 200) {
var data = new Uint8Array(xhr.response);
fs.writeFile(xfn, data, { encoding: 'binary' });
putWorkFile(xfn, data);
console.log(":::", xfn, data.length);
} else {
throw Error("Could not load extra file " + xpath);
}
}
}
}
export function staleFiles(step: BuildStep, targets: string[]) {
if (!step.maxts) throw Error("call populateFiles() first");
// see if any target files are more recent than inputs
for (var i = 0; i < targets.length; i++) {
var entry = store.workfs[targets[i]];
if (!entry || step.maxts > entry.ts)
return true;
}
console.log("unchanged", step.maxts, targets);
return false;
}
export function anyTargetChanged(step: BuildStep, targets: string[]) {
if (!step.maxts) throw Error("call populateFiles() first");
// see if any target files are more recent than inputs
for (var i = 0; i < targets.length; i++) {
var entry = store.workfs[targets[i]];
if (!entry || entry.ts > step.maxts)
return true;
}
console.log("unchanged", step.maxts, targets);
return false;
}
export function fixParamsWithDefines(path: string, params) {
var libargs = params.libargs;
if (path && libargs) {
var code = getWorkFileAsString(path);
if (code) {
var oldcfgfile = params.cfgfile;
var ident2index = {};
// find all lib args "IDENT=VALUE"
for (var i = 0; i < libargs.length; i++) {
var toks = libargs[i].split('=');
if (toks.length == 2) {
ident2index[toks[0]] = i;
}
}
// find #defines and replace them
var re = /^[;]?#define\s+(\w+)\s+(\S+)/gmi; // TODO: empty string?
var m;
while (m = re.exec(code)) {
var ident = m[1];
var value = m[2];
var index = ident2index[ident];
if (index >= 0) {
libargs[index] = ident + "=" + value;
console.log('Using libargs', index, libargs[index]);
// TODO: MMC3 mapper switch
if (ident == 'NES_MAPPER' && value == '4') {
params.cfgfile = 'nesbanked.cfg';
console.log("using config file", params.cfgfile);
}
} else if (ident == 'CFGFILE' && value) {
params.cfgfile = value;
} else if (ident == 'LIBARGS' && value) {
params.libargs = value.split(',').filter((s) => { return s != ''; });
console.log('Using libargs', params.libargs);
} else if (ident == 'CC65_FLAGS' && value) {
params.extra_compiler_args = value.split(',').filter((s) => { return s != ''; });
console.log('Using compiler flags', params.extra_compiler_args);
}
}
}
}
}
export function processEmbedDirective(code: string) {
let re3 = /^\s*#embed\s+"(.+?)"/gm;
// find #embed "filename.bin" and replace with C array data
return code.replace(re3, (m, m1) => {
let filename = m1;
let filedata = store.getFileData(filename);
let bytes = convertDataToUint8Array(filedata);
if (!bytes) throw new Error('#embed: file not found: "' + filename + '"');
let out = '';
for (let i = 0; i < bytes.length; i++) {
out += bytes[i].toString() + ',';
}
return out.substring(0, out.length-1);
});
}

BIN
src/worker/fs/arm32-fs.zip Normal file

Binary file not shown.

BIN
src/worker/fs/cc7800-fs.zip Executable file

Binary file not shown.

View File

@ -0,0 +1,27 @@
extern void _end;
void malloc_addblock(void* addr, int size);
int entry();
__attribute__((weak, naked, noinline, noreturn)) void _start() {
// set bss segment symbols
asm(".global __bss_start__, __bss_end__");
asm("__bss_start__ = _edata");
asm("__bss_end__ = _end");
// set stack pointer
asm("mov sp, #0x100000");
// allocate heap block
malloc_addblock(&_end, (void*)0xf0000 - &_end);
// run main()
entry();
// wait for next video frame
while (*(volatile int*)0x4000020 != 0) { }
// halt cpu
asm(".long 0xe7f000f0"); // udf #0
}
void _Exit(int ec) {
asm(".long 0xe7f000f0"); // udf #0
}

BIN
src/worker/lib/arm32/libc.a Normal file

Binary file not shown.

BIN
src/worker/lib/exidy/crt0.o Normal file

Binary file not shown.

View File

@ -0,0 +1,96 @@
; Startup code for cc65 and Shiru's NES library
; based on code by Groepaz/Hitmen <groepaz@gmx.net>, Ullrich von Bassewitz <uz@cc65.org>
; edited by Steven Hugg for Exidy
.export _exit,__STARTUP__:absolute=1
.export _HandyRTI
.exportzp _INTVEC
.export NMI,IRQ,START
.import initlib,push0,popa,popax,_main,zerobss,copydata
.importzp sp
; Linker generated symbols
.import __RAM0_START__ ,__RAM0_SIZE__
.import __ROM_START__ ,__ROM_SIZE__
.import __STARTUP_LOAD__,__STARTUP_RUN__,__STARTUP_SIZE__
.import __CODE_LOAD__ ,__CODE_RUN__ ,__CODE_SIZE__
.import __RODATA_LOAD__ ,__RODATA_RUN__ ,__RODATA_SIZE__
.segment "ZEROPAGE"
_INTVEC: .res 2
.segment "STARTUP"
START:
_exit:
sei ;Disable interrupts
cld ;Clear decimal mode
ldx #$ff ;Setup stack pointer
txs
@irrwait:
lda $5103
dex
bne @irrwait
lda #$00 ;Clear Ram
sta $5100 ;Set sprites to #0
@2:
sta $0,x
sta $100,x
sta $200,x
sta $300,x
sta $4000,x
sta $4100,x
sta $4200,x
sta $4300,x
sta $6800,x
sta $6900,x
sta $6a00,x
sta $6b00,x
sta $6c00,x
sta $6d00,x
sta $6e00,x
sta $6f00,x
inx
bne @2
; copy data segment
jsr copydata
; initialize cc65 stack
lda #<(__RAM0_START__+__RAM0_SIZE__)
sta sp
lda #>(__RAM0_START__+__RAM0_SIZE__)
sta sp+1
; init CC65 library
jsr initlib
; set interrupt vector in ZP
lda #<_HandyRTI
sta _INTVEC
lda #>_HandyRTI
sta _INTVEC+1
cli ;Enable interrupts
; start main()
jmp _main ;no parameters
; interrupt handler
NMI:
IRQ:
jmp (_INTVEC)
_HandyRTI:
rti
; CPU vectors
.segment "VECTORS"
.word NMI ;$fffa vblank nmi
.word START ;$fffc reset
.word IRQ ;$fffe irq / brk

View File

@ -0,0 +1,50 @@
SYMBOLS {
__STACKSIZE__: type = weak, value = $0100;
}
MEMORY {
# Zero Page
ZP: file = "", start = $0000, size = $0100, type = rw, define = yes;
# RAM
RAM0: file = "", start = $200, size = $200, define = yes;
# ROM Bank
PRG: file = %O, start = $8000, size = $8000 - 6, fill = yes, define = yes;
# CPU Vectors
VECTORS: file = %O, start = $FFFA, size = $0006, fill = yes;
# Sprite Bitmaps
SPRITES: file = %O, start = $0, size = $800, fill = yes, define = yes;
# Audio ROM
AUDIO: file = %O, start = $5800, size = $2800, fill = yes, define = yes;
}
SEGMENTS {
ZEROPAGE: load = ZP, type = zp;
STARTUP: load = PRG, type = ro, define = yes;
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 = RAM0, type = rw, define = yes;
VECTORS: load = VECTORS, type = ro;
SPRITES: load = SPRITES, type = ro, optional = yes;
BSS: load = RAM0, type = bss, define = yes;
RAM: load = RAM0, type = rw, optional = yes;
AUDIO: load = AUDIO, type = ro, optional = yes;
}
FEATURES {
CONDES: type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__,
segment = ONCE;
CONDES: type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__,
segment = RODATA;
CONDES: type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__,
segment = RODATA,
import = __CALLIRQ__;
}

Some files were not shown because too many files have changed in this diff Show More