Compare commits
43 Commits
libretro-w
...
master
Author | SHA1 | Date |
---|---|---|
Steven Hugg | d733a2cd27 | |
Steven Hugg | 098dcda93a | |
Steven Hugg | 886e19611e | |
Steven Hugg | 02986aed38 | |
Steven Hugg | d5b5734ef9 | |
Steven Hugg | 579e58e966 | |
Steven Hugg | 47a7aa5a83 | |
Steven Hugg | 9142328468 | |
Steven Hugg | 70eee2bdae | |
Steven Hugg | 949e216c69 | |
Steven Hugg | b160fb2ef2 | |
Steven Hugg | 63ee25741b | |
Steven Hugg | 8bdbae36e3 | |
Steven Hugg | c0909bef1b | |
Steven Hugg | 8023d56b88 | |
Steven Hugg | 45bc17d7ee | |
Steven Hugg | 8452fe73a1 | |
Steven Hugg | dff5c73d6a | |
Steven Hugg | f8462de014 | |
Steven Hugg | d08d73f422 | |
Steven Hugg | 0ede0e514b | |
Steven Hugg | dbe73c4fb2 | |
Steven Hugg | 9ecfb3cfa8 | |
Steven Hugg | 312cb3d025 | |
Steven Hugg | bd63ef1268 | |
Steven Hugg | 2e0382b0a6 | |
Steven Hugg | 611c174aed | |
Steven Hugg | d9001df5d4 | |
Steven Hugg | e6c3dc98e1 | |
Steven Hugg | 43d844fa79 | |
Steven Hugg | c5bcd8ad9b | |
Steven Hugg | c189875be3 | |
Steven Hugg | 16fcf33881 | |
Steven Hugg | 8091985fde | |
Steven Hugg | c9354a83ea | |
Steven Hugg | 73c7ac5941 | |
Steven Hugg | 19e3bbbea3 | |
Steven Hugg | 12957d7740 | |
Steven Hugg | a252ea65bd | |
Steven Hugg | 1c0b3e2fdd | |
Steven Hugg | 44271fe9b8 | |
Steven Hugg | f6452a719f | |
Steven Hugg | de6250b0cd |
|
@ -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'
|
||||
|
|
20
README.md
20
README.md
|
@ -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)
|
||||
|
||||
|
|
11
css/ui.css
11
css/ui.css
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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">×</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">×</button>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9d124f087e1f0c7f74f9244b9679cc62e71fa524
|
||||
Subproject commit 113cd5741e5c414bbbe47ef8be7a896652d48f64
|
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
@ -0,0 +1,7 @@
|
|||
|
||||
#define SERIAL_OUT ((int*)0x4000048)
|
||||
|
||||
void putchar_(char c) {
|
||||
*SERIAL_OUT = c;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
* = $0801
|
||||
!word Start
|
||||
!byte $00,$00,$9e
|
||||
!text "2066"
|
||||
!byte $00,$00,$00
|
||||
* = $0812
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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;
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)))
|
||||
|
|
|
@ -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"
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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; },
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
@ -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: '/' });
|
||||
}
|
||||
|
716
src/ide/ui.ts
716
src/ide/ui.ts
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'},
|
||||
] } };
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -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)'},
|
||||
|
|
|
@ -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)'},
|
||||
];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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)'},
|
||||
];
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)'},
|
||||
];
|
||||
|
||||
|
|
|
@ -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:[
|
||||
|
|
|
@ -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);
|
||||
*/
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
*/
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in New Issue