Merge remote-tracking branch 'origin/master' into fredsa-conflict-fix

# Conflicts:
#	src/ide/views/asseteditor.ts
This commit is contained in:
Steven Hugg committed 2026-08-14 14:31:36 +02:00
commit be56f7ea68
51 files changed
+2943 -98

No files matched your search

+10 -2
View File
@@ -57,9 +57,17 @@ distro: buildtsc
rm -r $(TMP)/doc $(TMP)/scripts $(TMP)/test* $(TMP)/tools $(TMP)/.[a-z]* $(TMP)/ts*.json # $(TMP)/meta
rm -f $(TMP)/javatari && mkdir -p $(TMP)/javatari && cp -p javatari.js/release/javatari/* $(TMP)/javatari/
tsweb: submodules node_modules
getip:
@if command -v ip > /dev/null; then \
ip addr | grep -w inet; \
elif command -v ifconfig > /dev/null; then \
ifconfig | grep -w inet; \
else \
ipconfig | grep IPv4; \
fi
tsweb: submodules node_modules getip
npm run esbuild-clean
(ip addr || ifconfig) | grep inet
trap 'kill 0' EXIT; \
make buildgrammars; \
$(TSC) -w --preserveWatchOutput & \
+3 -1
View File
@@ -20,6 +20,8 @@ git clone -b master --single-branch git@github.com:sehugg/8bitworkshop.git
To build the 8bitworkshop IDE:
```sh
# On Windows, use the `UCRT64` shell from https://msys2.org/
# On Linux/macOS, just type:
make
```
@@ -51,7 +53,7 @@ Copyright © 2016-2026 [Steven E. Hugg](https://github.com/sehugg).
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.
This project includes various dependencies, modules, and components that retain their original licenses.
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
+1 -1
View File
@@ -60,7 +60,7 @@ body {
<li class="dropdown dropdown-submenu">
<a tabindex="-1" href="javascript:void(0)">Sync</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_login">Sign in to Github...</a></li>
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_login">Sign in to GitHub...</a></li>
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_logout">Log out</a></li>
<hr>
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_import">Import Project from GitHub...</a></li>
+30
View File
@@ -0,0 +1,30 @@
* = $7ffe
; 2-byte load address for ROM image
.word $8000
; http://swut.net/c64cart-howto.html
; https://codebase64.org/doku.php?id=base:assembling_your_own_cart_rom_image
.word CartKReset ; cold start vector
.word CartWStart ; warm start vector
.byt $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
.dsb 12, $ea ; nop
#endif
CartWStart
; should be * = $x025
+41
View File
@@ -0,0 +1,41 @@
#include "cartheader.xa"
Temp = $02
Start
sei ; turn off interrupts
ldy #0
Loop
lda Message,y ; load message byte
beq EOM ; 0 = end of string
clc
adc #$c0 ; convert PETSCII to screen code
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
; PETSCII - http://sta.c64.org/cbm64pet.html
.asc "HELLO WORLD"
.byt 0
+1430
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef __BCD_H_INCLUDE
#define __BCD_H_INCLUDE
#include <types.h>
#include "types.h"
#include <stdint.h>
/** @file bcd.h
+1 -1
View File
@@ -17,7 +17,7 @@
#ifndef _CGB_H
#define _CGB_H
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Macro to create a CGB palette color entry out of 5-bit color components.
+2 -2
View File
@@ -30,7 +30,7 @@
#ifndef __DRAWING_H
#define __DRAWING_H
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Size of the screen in pixels */
@@ -56,7 +56,7 @@
#define SIGNED 1
#define UNSIGNED 0
#include <types.h>
#include "types.h"
/** Print the string 'str' with no interpretation
@see gotogxy()
+1 -1
View File
@@ -12,7 +12,7 @@
#ifndef __EMU_DEBUG_INCLUDE
#define __EMU_DEBUG_INCLUDE
#include <types.h>
#include "types.h"
/** Macro to display a message in the emulator debug message window
+1 -1
View File
@@ -8,7 +8,7 @@
#ifndef __GBDECOMPRESS_H_INCLUDE
#define __GBDECOMPRESS_H_INCLUDE
#include <types.h>
#include "types.h"
#include <stdint.h>
/** gb-decompress data from sour into dest
+1 -1
View File
@@ -10,7 +10,7 @@
#define _ISR_H_INCLUDE_
#include <stdint.h>
#include <types.h>
#include "types.h"
// #define VECTOR_VBL 0x40 // you can not define raw vector for VBlank interrupt
#define VECTOR_STAT 0x48 /**< Address for the STAT interrupt vector */
+1 -1
View File
@@ -68,7 +68,7 @@
#define _METASPRITES_H_INCLUDE
#include <gb/hardware.h>
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Metasprite sub-item structure
+1 -1
View File
@@ -6,7 +6,7 @@
#ifndef _SGB_H
#define _SGB_H
#include <types.h>
#include "types.h"
#include <stdint.h>
#define SGB_PAL_01 0x00U /**< SGB Command: Set SGB Palettes 0 & 1 */
+1 -1
View File
@@ -1,5 +1,5 @@
.include "gingerbread.sgb"
.include "gb/gingerbread.sgb"
; Set the size of the ROM file here. 0 means 32 kB, 1 means 64 kB, 2 means 128 kB and so on.
ROM_SIZE = 1
+631
View File
@@ -0,0 +1,631 @@
/*
* Paku Paku for Game Boy — port of presets/pacman/pakupaku.c
*
* One-lane chase inspired by crisp-game-lib "PAKU PAKU":
* A / Start = reverse Left / Right = face that way
* Collect dots; power pellet makes the ghost edible.
* Movement/AI match the Pac-Man hardware port; graphics from pacman.5e/5f
* (regenerate with: python3 scripts/gen_gb_pakupaku_gfx.py).
*/
//#link "gb/sfr.sgb"
//#link "gb/crt0.sgb"
//#resource "gb/global.sgb"
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
typedef uint8_t byte;
typedef uint16_t word;
typedef int8_t sbyte;
#define FIELD_MAX 152 /* arcade 208 scaled to GB width */
#define PLAYER_SPD 2
#define NUM_DOTS 18
#define HIT_DIST 12
#define ROW_ABOVE 8
#define DOT_TILE_Y 9
#define ROW_BELOW 10
#define CORRIDOR_Y 68
/* BG tiles from pacman.5e (after font 0..34) */
#define BKG_BASE 40
#define T_WALL_ABOVE (BKG_BASE + 0)
#define T_WALL_BELOW (BKG_BASE + 1)
#define T_DOT (BKG_BASE + 2)
#define T_POWER (BKG_BASE + 3)
/*
* Arcade pacman.5f sprites → GB 16×16 (two 8×16 OAM each).
* Each frame is 4 tiles at SPR_BASE: L-top, L-bot, R-top, R-bot.
* Regenerate: python3 scripts/gen_gb_pakupaku_gfx.py
*/
#define SPR_BASE 48
#define SPR_FRAME(i) ((byte)(SPR_BASE + (i) * 4))
#define SP_OPEN SPR_FRAME(0)
#define SP_MID SPR_FRAME(1)
#define SP_CLOSED SPR_FRAME(2)
#define SP_DEATH0 SPR_FRAME(3) /* 11 frames: 3..13 */
#define SP_DEATH_N 11
#define SP_GHOST0 SPR_FRAME(14)
#define SP_GHOST1 SPR_FRAME(15)
#define SP_SCARED0 SPR_FRAME(16)
#define SP_SCARED1 SPR_FRAME(17)
#define SP_EYES SPR_FRAME(18)
#define SPR_TILE_COUNT 76
typedef struct {
byte tx;
byte active;
byte is_power;
} Dot;
word rnd = 0xCACE;
word score;
byte multiplier;
byte player_x;
sbyte player_vx;
byte enemy_x;
sbyte enemy_eye_vx;
word power_ticks;
word anim_ticks;
byte btn_prev;
byte game_over;
byte waka;
Dot dots[NUM_DOTS];
byte joy_left, joy_right, joy_fire, joy_start;
byte sfx_timer;
byte sfx_kind; /* 0=off 1/2=waka 3=eat 4=death 6=power */
byte death_phase;
const uint8_t font_1bpp[] = {
/* 0 space */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* 1 '0' */ 0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00,
/* 2 '1' */ 0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00,
/* 3 '2' */ 0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00,
/* 4 '3' */ 0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00,
/* 5 '4' */ 0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00,
/* 6 '5' */ 0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00,
/* 7 '6' */ 0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00,
/* 8 '7' */ 0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00,
/* 9 '8' */ 0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00,
/*10 '9' */ 0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00,
/*11 'A' */ 0x3C,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
/*12 'C' */ 0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00,
/*13 'E' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00,
/*14 'F' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00,
/*15 'G' */ 0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00,
/*16 'H' */ 0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
/*17 'I' */ 0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,
/*18 'K' */ 0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00,
/*19 'L' */ 0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00,
/*20 'M' */ 0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00,
/*21 'N' */ 0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00,
/*22 'O' */ 0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
/*23 'P' */ 0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00,
/*24 'R' */ 0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00,
/*25 'S' */ 0x3C,0x66,0x60,0x3C,0x06,0x66,0x3C,0x00,
/*26 'T' */ 0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00,
/*27 'U' */ 0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
/*28 'V' */ 0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00,
/*29 'W' */ 0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00,
/*30 'X' */ 0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00,
/*31 'D' */ 0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00,
/*32 '-' */ 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,
/*33 '!' */ 0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00,
/*34 '/' */ 0x06,0x06,0x0C,0x18,0x30,0x60,0x60,0x00,
};
const uint8_t bkg_tiles[] = {
/*{w:8,h:8,bpp:1,count:4,brev:1,np:2,pofs:1,sl:2}*/
0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x3c,0x00,0x7e,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x7e,0x00,0x3c,0x00,
};
const uint8_t sprite_tiles[] = {
/*{w:8,h:8,bpp:1,count:76,brev:1,np:2,pofs:1,sl:2}*/
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7e,0x7e,0x7c,0x7c,0x78,0x78,
0x7c,0x7c,0x7e,0x7e,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x78,0x78,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,
0x00,0x00,0xe0,0xe0,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x70,0x70,0x78,0x78,0x7c,0x7c,0x7e,0x7e,
0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x1c,0x1c,0x3c,0x3c,0x7c,0x7c,0xfc,0xfc,
0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0xf0,0xf0,0xf8,0xf8,0xfe,0xfe,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x1e,0x1e,0x3e,0x3e,0xfe,0xfe,
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0xfc,0xfc,
0xff,0xff,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x0e,0x7e,0x7e,
0xfe,0xfe,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,
0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0x1e,
0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,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,
0x0f,0x0f,0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xe0,0xe0,0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x03,0x0f,0x0f,0x7f,0x7f,0xff,0xff,0x7e,0x7e,0x1c,0x1c,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0xe0,0xe0,0xfc,0xfc,0xfe,0xfe,0xfc,0xfc,0x70,0x70,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x7f,0x7f,0x7e,0x7e,0x3c,0x3c,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x80,0xe0,0xe0,0xf0,0xf0,0xfc,0xfc,0xfc,0xfc,0x78,0x78,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x03,0x03,0x07,0x07,0x07,0x07,0x0f,0x0f,0x1f,0x1f,0x0e,0x0e,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0xe0,0xe0,0xf0,0xf0,0xe0,0xe0,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x01,0x03,0x03,0x03,0x03,0x03,0x03,0x07,0x07,0x02,0x02,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0xc0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x12,0x12,
0x08,0x08,0x00,0x00,0x30,0x30,0x00,0x00,0x08,0x08,0x10,0x10,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80,0x80,
0x10,0x10,0x20,0x20,0x00,0x00,0x18,0x18,0x00,0x00,0x20,0x20,0x90,0x90,0x40,0x40,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x0f,0x00,0x0c,0x00,0x0c,0x00,
0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x3c,0x00,0x30,0x00,0x30,0x00,
0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
byte rand8(void) {
rnd = rnd * 17 + 53;
return (byte)(rnd >> 8);
}
byte abs_diff(byte a, byte b) {
return (a > b) ? (byte)(a - b) : (byte)(b - a);
}
byte glyph(char c) {
if (c == ' ') return 0;
if (c >= '0' && c <= '9') return (byte)(1 + (c - '0'));
switch (c) {
case 'A': return 11; case 'C': return 12; case 'E': return 13;
case 'F': return 14; case 'G': return 15; case 'H': return 16;
case 'I': return 17; case 'K': return 18; case 'L': return 19;
case 'M': return 20; case 'N': return 21; case 'O': return 22;
case 'P': return 23; case 'R': return 24; case 'S': return 25;
case 'T': return 26; case 'U': return 27; case 'V': return 28;
case 'W': return 29; case 'X': return 30; case 'D': return 31;
case '-': return 32; case '!': return 33; case '/': return 34;
default: return 0;
}
}
void put_str(byte x, byte y, const char* s) {
while (*s) set_bkg_tile_xy(x++, y, glyph(*s++));
}
void put_digit(byte x, byte y, byte d) {
set_bkg_tile_xy(x, y, (byte)(1 + (d % 10)));
}
void clrscr(void) {
byte x, y;
for (y = 0; y < 32; y++)
for (x = 0; x < 32; x++)
set_bkg_tile_xy(x, y, 0);
}
void hide_all_sprites(void) {
byte i;
for (i = 0; i < 40; i++) move_sprite(i, 0, 0);
}
void read_controls(void) {
byte j = joypad();
joy_left = (j & J_LEFT) != 0;
joy_right = (j & J_RIGHT) != 0;
joy_fire = (j & J_A) != 0;
joy_start = (j & J_START) != 0;
}
void psg_init(void) {
NR52_REG = 0x80;
NR50_REG = 0x77;
NR51_REG = 0xFF;
NR10_REG = 0x00;
}
void sfx_off(void) {
sfx_timer = 0;
sfx_kind = 0;
NR12_REG = 0x00;
NR14_REG = 0x80;
NR22_REG = 0x00;
NR24_REG = 0x80;
}
void beep(word per, byte vol, byte len) {
/* ch1 one-shot */
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = (byte)((vol << 4) | 0x01);
NR13_REG = (byte)(per & 0xff);
NR14_REG = (byte)(0x80 | ((per >> 8) & 0x07));
sfx_timer = len;
}
void play_sfx(byte id) {
sfx_kind = id;
if (id == 4) death_phase = 0;
switch (id) {
case 1: beep(0x6C0, 10, 3); break; /* waka hi */
case 2: beep(0x650, 9, 3); break; /* waka lo */
case 3: beep(0x700, 12, 12); break; /* eat ghost */
case 4: beep(0x480, 12, 40); break; /* death */
case 6: beep(0x720, 11, 8); break; /* power */
default: sfx_off(); break;
}
}
void tick_sfx(void) {
if (!sfx_timer) return;
--sfx_timer;
if (!sfx_timer && sfx_kind == 4) {
death_phase++;
if (death_phase < 4) {
beep((word)(0x480 - death_phase * 0x40), 10, 10);
sfx_kind = 4;
} else {
death_phase = 0;
sfx_kind = 0;
}
}
}
void wait_vblank(void) {
wait_vbl_done();
tick_sfx();
}
void wait_frames(byte n) {
while (n--) wait_vblank();
}
void wait_for_button(void) {
read_controls();
while (!joy_fire && !joy_start) { wait_vblank(); read_controls(); }
while (joy_fire || joy_start) { wait_vblank(); read_controls(); }
}
void draw_hud(void) {
put_str(0, 0, "PAKU PAKU");
put_str(0, 1, "SCORE");
put_digit(6, 1, (byte)((score / 1000) % 10));
put_digit(7, 1, (byte)((score / 100) % 10));
put_digit(8, 1, (byte)((score / 10) % 10));
put_digit(9, 1, (byte)(score % 10));
put_str(12, 1, "X");
put_digit(13, 1, multiplier > 9 ? 9 : multiplier);
put_str(0, 17, "A=TURN");
}
void draw_corridor(void) {
byte x;
for (x = 0; x < 20; x++) {
set_bkg_tile_xy(x, ROW_ABOVE, T_WALL_ABOVE);
set_bkg_tile_xy(x, DOT_TILE_Y, 0);
set_bkg_tile_xy(x, ROW_BELOW, T_WALL_BELOW);
}
}
void add_dots(void) {
byte i, pi;
pi = (player_x > 80) ? (byte)(1 + (rand8() % 7)) : (byte)(10 + (rand8() % 7));
for (i = 0; i < NUM_DOTS; i++) {
dots[i].tx = (byte)(i + 1);
dots[i].active = 1;
dots[i].is_power = (i == pi);
}
if (multiplier < 99) multiplier++;
}
void draw_dots(void) {
byte i;
for (i = 0; i < NUM_DOTS; i++) {
if (!dots[i].active) {
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
continue;
}
if (dots[i].is_power) {
if ((anim_ticks >> 3) & 1)
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_POWER);
else
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
} else {
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_DOT);
}
}
}
void collect_dots(void) {
byte i, ptx;
ptx = (byte)((player_x + 8) >> 3);
if (ptx < 1) ptx = 1;
if (ptx > NUM_DOTS) ptx = NUM_DOTS;
for (i = 0; i < NUM_DOTS; i++) {
if (!dots[i].active) continue;
if (dots[i].tx != ptx) continue;
dots[i].active = 0;
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
if (dots[i].is_power) {
play_sfx(6);
if (!enemy_eye_vx) power_ticks = 200;
} else {
waka ^= 1;
play_sfx(waka ? 1 : 2);
}
score += multiplier;
}
}
byte dots_left(void) {
byte i, n = 0;
for (i = 0; i < NUM_DOTS; i++)
if (dots[i].active) n++;
return n;
}
byte pac_frame(void) {
byte ai = (byte)(anim_ticks & 3);
if (ai == 0) return SP_OPEN;
if (ai == 1) return SP_MID;
if (ai == 2) return SP_CLOSED;
return SP_MID;
}
void put_actor16(byte i0, byte frame, byte flip, byte prop, byte x, byte y) {
/* Two 8×16 sprites = one 16×16 arcade sprite; swap halves when flipped. */
byte i1 = (byte)(i0 + 1);
byte p = prop;
byte oam0, oam1;
if (flip) p |= S_FLIPX;
set_sprite_tile(i0, frame);
set_sprite_tile(i1, (byte)(frame + 2));
set_sprite_prop(i0, p);
set_sprite_prop(i1, p);
if (flip) {
oam0 = (byte)(x + 16);
oam1 = (byte)(x + 8);
} else {
oam0 = (byte)(x + 8);
oam1 = (byte)(x + 16);
}
/* Hide if past the right edge (OAM X wraps). */
if (x > 152) {
move_sprite(i0, 0, 0);
move_sprite(i1, 0, 0);
return;
}
move_sprite(i0, oam0, (byte)(y + 16));
move_sprite(i1, oam1, (byte)(y + 16));
}
void draw_actors(void) {
byte ghost, gprop, pflip, gflip;
sbyte evx;
pflip = (player_vx < 0) ? 1 : 0;
put_actor16(0, pac_frame(), pflip, 0, player_x, CORRIDOR_Y);
if (enemy_eye_vx) {
ghost = SP_EYES;
gflip = (enemy_eye_vx < 0) ? 1 : 0;
gprop = 0;
} else if (power_ticks) {
ghost = ((anim_ticks >> 1) & 1) ? SP_SCARED1 : SP_SCARED0;
gflip = 0;
gprop = 0;
/* blink near end of power */
if (power_ticks < 60 && (anim_ticks & 0x08)) {
move_sprite(2, 0, 0);
move_sprite(3, 0, 0);
return;
}
} else {
ghost = ((anim_ticks >> 1) & 1) ? SP_GHOST1 : SP_GHOST0;
evx = (player_x > enemy_x) ? 1 : -1;
gflip = (evx < 0) ? 1 : 0;
gprop = 0;
}
put_actor16(2, ghost, gflip, gprop, enemy_x, CORRIDOR_Y);
}
void title_screen(void) {
byte x;
clrscr();
hide_all_sprites();
move_bkg(0, 0);
/* corridor stripe behind the demo sprites */
for (x = 0; x < 20; x++) {
set_bkg_tile_xy(x, 6, T_WALL_ABOVE);
set_bkg_tile_xy(x, 7, 0);
set_bkg_tile_xy(x, 8, T_WALL_BELOW);
}
put_str(5, 2, "PAKU PAKU");
put_actor16(0, SP_OPEN, 0, 0, 52, 48);
put_actor16(2, SP_GHOST0, 0, 0, 92, 48);
put_str(4, 11, "A TO TURN");
put_str(2, 13, "EAT DOTS EAT GHOST");
put_str(4, 16, "PRESS START");
wait_for_button();
sfx_off();
}
void show_game_over(void) {
byte frame_i, t, frame;
hide_all_sprites();
put_actor16(0, SP_OPEN, 0, 0, player_x, CORRIDOR_Y);
play_sfx(4);
/* Arcade: 11 frames × 8 vblanks */
for (frame_i = 0; frame_i < SP_DEATH_N; frame_i++) {
frame = (byte)(SP_DEATH0 + (byte)(frame_i * 4));
for (t = 0; t < 8; t++) {
put_actor16(0, frame, 0, 0, player_x, CORRIDOR_Y);
wait_vblank();
read_controls();
if (joy_fire || joy_start) goto death_done;
}
}
death_done:
hide_all_sprites();
put_str(5, 7, "GAME OVER");
put_str(4, 10, "PRESS START");
wait_for_button();
sfx_off();
}
void reset_round(void) {
player_x = 48;
player_vx = 1;
enemy_x = 128;
enemy_eye_vx = 0;
power_ticks = 0;
anim_ticks = 0;
btn_prev = 1;
game_over = 0;
waka = 0;
sfx_off();
draw_corridor();
add_dots();
}
void game_loop(void) {
byte btn;
sbyte evx;
byte espd;
clrscr();
hide_all_sprites();
move_bkg(0, 0);
draw_corridor();
draw_hud();
reset_round();
while (!game_over) {
wait_vblank();
anim_ticks++;
read_controls();
btn = (joy_fire || joy_start) ? 1 : 0;
if (btn && !btn_prev) player_vx = (sbyte)-player_vx;
btn_prev = btn;
if (joy_left) player_vx = -1;
if (joy_right) player_vx = 1;
/* Same wrap as pacman pakupaku.c */
player_x = (byte)(player_x + player_vx * PLAYER_SPD);
if (player_x < 4) player_x = FIELD_MAX;
else if (player_x > FIELD_MAX) player_x = 4;
collect_dots();
if (!dots_left()) add_dots();
/* Ghost AI — identical rules to presets/pacman/pakupaku.c */
if (enemy_eye_vx) {
evx = enemy_eye_vx;
espd = 3;
} else if (power_ticks) {
evx = (player_x > enemy_x) ? (sbyte)-1 : 1;
espd = 1;
} else {
evx = (player_x > enemy_x) ? 1 : -1;
espd = 2;
}
if (evx > 0) {
if (enemy_x < FIELD_MAX - 2) enemy_x = (byte)(enemy_x + espd);
else if (enemy_eye_vx) enemy_eye_vx = 0;
} else {
if (enemy_x > 6) enemy_x = (byte)(enemy_x - espd);
else if (enemy_eye_vx) enemy_eye_vx = 0;
}
if (!enemy_eye_vx && abs_diff(player_x, enemy_x) < HIT_DIST) {
if (power_ticks) {
play_sfx(3);
score += (word)(10 * (multiplier ? multiplier : 1));
enemy_eye_vx = (player_x > 80) ? (sbyte)-1 : 1;
power_ticks = 0;
if (multiplier < 99) multiplier++;
} else {
game_over = 1;
}
}
if (power_ticks) power_ticks--;
draw_dots();
draw_hud();
draw_actors();
}
}
void setup_graphics(void) {
DISPLAY_OFF;
set_bkg_1bpp_data(0, 35, font_1bpp);
set_bkg_data(BKG_BASE, 4, bkg_tiles);
set_sprite_data(SPR_BASE, SPR_TILE_COUNT, sprite_tiles);
SPRITES_8x16;
BGP_REG = 0xE4;
OBP0_REG = 0xE4;
OBP1_REG = 0xE4;
psg_init();
HIDE_WIN;
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
}
void main(void) {
setup_graphics();
score = 0;
multiplier = 0;
while (1) {
title_screen();
score = 0;
multiplier = 0;
game_loop();
show_game_over();
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ of text
If you DO want to add soft line breaks (which translate to `<br>` in HTML) to a paragraph,
you can do so by adding 3 space characters to the end of the line (` `).
You can also force every line break in paragraphs to translate to `<br>` (as Github does) by
You can also force every line break in paragraphs to translate to `<br>` (as GitHub does) by
enabling the option **`simpleLineBreaks`**.
## Headings
File renamed without changes.
+195
View File
@@ -0,0 +1,195 @@
(story title) Cloak of Darkness
(story author) Linus Åkesson
(story noun) A port of Roger Firth's reference game
(story ifid)
%% This is the IFID for the Dialog port of Cloak of Darkness:
A5AA4F02-8F50-4649-A4BD-B1B5C5408B67
(story release 3)
(scoring enabled)
(maximum score 2)
(library links enabled)
(default actions enabled)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
(intro)
Hurrying through the rainswept November night, you're glad to
see the bright lights of the Opera House. It's surprising that
there aren't more people about but, hey, what do you expect in
a cheap demo game...?
(banner)
(enter #foyer)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#foyer
(room *)
(singleton *)
(name *) foyer of the Opera House
(look *)
(link [me]) {You} are standing in a spacious hall, splendidly
decorated in red and gold, with glittering chandeliers
overhead. The entrance from the street is to the (#north), and
there are doorways (#south) and (#west).
(prevent [leave * #north])
You've only just arrived, and besides, the weather outside
seems to be getting worse.
(from * go #west to #cloakroom)
(from * go #south to #bar)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#cloakroom
(room *)
(singleton *)
(name *) cloakroom
(look *)
The walls of this small room were clearly once lined with
hooks, though now (#hook { only one }) remains. The exit is a
door to the (#east).
(notice #hook)
(from * go #east to #foyer)
#hook
(supporter *)
(name *) small brass hook
(dict *) peg
(* is #in #cloakroom)
(descr *)
It's just a small brass hook,
(collect $Obj) ($Obj is #on *) (into $List)
(if) (nonempty $List) (then)
with (a $List) hanging on it.
(else)
screwed to the wall.
(endif)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#bar
(room *)
(singleton *)
(name *) Foyer Bar
(look *)
The bar, much rougher than you'd have guessed after the
opulence of the foyer to the north, is completely empty. There
seems to be some sort of (#message {message}) scrawled in the
sawdust on the floor.
(notice #message)
(from * go #north to #foyer)
(inherently dark *)
(#cloak is in room *)
(instead of [look #down])
(current room *)
(try [examine #message])
#message
(name *) scrawled message
(dict *) floor sawdust
(* is #in #bar)
(descr *)
(if) (message has been trampled) (then)
The message has been carelessly trampled, making it
difficult to read. You can just distinguish the
words...
(par)
(game over { You have lost })
(else)
(increase score by 1)
The message, neatly marked in the sawdust, reads...
(par)
(game over { You have won })
(endif)
~(warned the player about darkness)
(prevent $Action)
(current room #bar)
~(player can see)
~(command $Action)
($Action = [$Verb | $])
~($Verb is one of [go leave enter look])
(if) (warned the player about darkness) (then)
Blundering around in the dark isn't a good idea!
(now) (message has been trampled)
(else)
In the dark? You could easily disturb something.
(now) (warned the player about darkness)
(endif)
(instead of [read *])
(try [examine *])
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#player
(current player *)
(* is #in #foyer)
(instead of [examine *])
(try [inventory])
#cloak
(name *) velvet cloak
(dict *) dark black satin
(wearable *)
(* is #wornby #player)
(descr *)
A handsome cloak, of velvet trimmed with satin, and slightly
splattered with raindrops. Its blackness is so deep that it
almost seems to suck light from the room.
(prevent [drop *])
(when dropping outside cloakroom)
(prevent [put * $ $])
(when dropping outside cloakroom)
(when dropping outside cloakroom)
~(current room #cloakroom)
This isn't the best place to leave a smart cloak lying around.
(perform [put * #on #hook])
~(hook point awarded)
(increase score by 1)
(now) (hook point awarded)
(fail) %% Fall back on default behaviour now.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
(understand [hang | $Words] as [put $Obj #on $Dest])
*(split $Words by [on] into $Left and $Right)
*(understand $Left as object $Obj preferably held)
*(understand $Right as single object $Dest)
+48
View File
@@ -0,0 +1,48 @@
%% Hello World, in the Dialog interactive fiction language.
%% https://linusakesson.net/dialog/docs/
%% Story metadata
(story title) Hello World
(story author) 8bitworkshop
(story noun) A one-room Dialog demo
%% (intro) runs when the game starts.
(intro)
Welcome to Dialog! Try LOOK, EXAMINE ME, or TURN ON LAMP.
(par)
(banner)
(enter #room)
%% Define an object: the player character.
%% The * token refers to the last object
%% labelled on the left margin (the current "topic")
#player
(current player *)
(descr *) You look marvelous.
%% The only room (for now).
#room
(room *)
(name *) small room
(look *)
This is a small, featureless room.
%% An object the player can pick up.
#lamp
(item *)
(name *) brass lamp
(dict *) lantern
(initial appearance *)
A brass lamp sits on the floor.
(descr *)
(* is on)
It is a battered brass lamp, switched on.
(descr *)
It is a battered brass lamp, currently off.
(* is #in #room)
(switchable *)
%% A rule: The lamp provides light when it is turned on.
(* provides light)
(* is on)
+3 -2
View File
@@ -441,6 +441,7 @@ export function getToolForFilename_6502(fn: string): string {
if (fn.endsWith(".ca65")) return "ca65";
if (fn.endsWith(".dasm")) return "dasm";
if (fn.endsWith(".acme")) return "acme";
if (fn.endsWith(".xa")) return "xa";
if (fn.endsWith(".wiz")) return "wiz";
if (fn.endsWith(".ecs")) return "ecs";
if (fn.endsWith(".cpp")) return "oscar64";
@@ -480,7 +481,7 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
return disassemble6502(pc, read(pc), read(pc + 1), read(pc + 2));
}
getToolForFilename = getToolForFilename_6502;
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; };
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; };
getDebugCategories() {
return ['CPU', 'ZPRAM', 'Stack'];
@@ -984,7 +985,7 @@ export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMac
getOpcodeMetadata = getOpcodeMetadata_6502;
getToolForFilename(fn) { return getToolForFilename_6502(fn); }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
disassemble(pc: number, read: (addr: number) => number): DisasmLine {
return disassemble6502(pc, read(pc), read(pc + 1), read(pc + 2));
+43
View File
@@ -0,0 +1,43 @@
/**
* Shared Game Boy DMG (olive LCD) palette.
*
* Shade 0 = lightest (LCD pixel off / "white"), shade 3 = darkest (pixel on).
* Values are packed for Canvas ImageData as Uint32 little-endian RGBA (= 0xAABBGGRR).
*/
export function rgbToPixel(r: number, g: number, b: number): number {
return 0xFF000000 | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff);
}
/** Four DMG shades, lightest → darkest (matches BGP/OBP shade indices). */
export const DMG_PALETTE: number[] = [
rgbToPixel(0xd0, 0xd8, 0x84), // Lightest (warm mint)
rgbToPixel(0x87, 0xa8, 0x4c), // Light mid
rgbToPixel(0x52, 0x6b, 0x34), // Dark mid
rgbToPixel(0x2d, 0x31, 0x22), // Darkest (deep olive)
];
/** RGB888 triples matching DMG_PALETTE (for boytacean remap / tooling). */
export const DMG_PALETTE_RGB: [number, number, number][] = [
[0xd0, 0xd8, 0x84],
[0x87, 0xa8, 0x4c],
[0x52, 0x6b, 0x34],
[0x2d, 0x31, 0x22],
];
/** Asset-editor / PREDEF_PALETTES entries (RGB without alpha; callers OR 0xff000000). */
export const DMG_PALETTE_RGB24: number[] = [
0xd0d884,
0x87a84c,
0x526b34,
0x2d3122,
];
/** Map a greyscale / near-grey RGB888 pixel to a DMG shade (0=light … 3=dark). */
export function dmgShadeFromRgb(r: number, g: number, b: number): number {
var y = (r + g + b) / 3;
if (y >= 192) return 0;
if (y >= 128) return 1;
if (y >= 64) return 2;
return 3;
}
+12
View File
@@ -624,6 +624,17 @@ export class WASIRunner {
const fd = this.fs.getFile(path);
console.log("path_filestat_get", dir+"", filename, path, filestat_ptr, '->', fd+"");
if (!fd) return WASIErrors.NOENT;
this.poke_filestat(filestat_ptr, fd);
return WASIErrors.SUCCESS;
}
fd_filestat_get(fd: number, filestat_ptr: number) {
const file = this.fds[fd];
debug("fd_filestat_get", fd, filestat_ptr, file + "");
if (file == null) return WASIErrors.BADF;
this.poke_filestat(filestat_ptr, file);
return WASIErrors.SUCCESS;
}
poke_filestat(filestat_ptr: number, fd: WASIFileDescriptor) {
this.poke64(filestat_ptr, fd.fdindex); // dev
this.poke64(filestat_ptr + 8, 0); // ino
this.poke8(filestat_ptr + 16, fd.type); // filetype
@@ -686,6 +697,7 @@ export class WASIRunner {
fd_seek: this.fd_seek.bind(this),
fd_close: this.fd_close.bind(this),
path_filestat_get: this.path_filestat_get.bind(this),
fd_filestat_get: this.fd_filestat_get.bind(this),
random_get: this.random_get.bind(this),
path_readlink: this.path_readlink.bind(this),
path_unlink_file: this.path_unlink_file.bind(this),
+10 -2
View File
@@ -18,7 +18,7 @@ export interface EditorContext {
}
export type SelectablePalette = {
node: PixNode
node: PixNode | null
name: string
palette: Uint32Array
}
@@ -238,7 +238,11 @@ export function validateAssetData(datastr: string, fmt): string | null {
maxOffset = Math.max(maxOffset, offset);
}
}
var required = maxOffset + (nplanes > 1 ? (nplanes - 1) * pofs : 0) + 1 + skip;
// Planar formats (e.g. NES pofs>=wpimg) store extra planes past each image block.
// Interleaved formats (GB/SMS pofs < wpimg) already include plane bytes in maxOffset.
var planeExtent = (nplanes > 1) ? (nplanes - 1) * pofs : 0;
if (planeExtent < wpimg) planeExtent = 0;
var required = maxOffset + planeExtent + 1 + skip;
if (words.length != required) {
return `Expected ${required} value(s), found ${words.length}`;
@@ -358,6 +362,10 @@ export function convertPaletteFormat(palbytes: UintArray, palfmt: PixelEditorPal
// TODO: illegal colors?
const PREDEF_PALETTES = {
// LCD polarity: index 0 = lightest (pixel off), 3 = darkest (pixel on)
'gb': [
0xd0d884, 0x87a84c, 0x526b34, 0x2d3122,
],
'nes': [
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
+10 -3
View File
@@ -42,7 +42,7 @@ export class NullFilesystem implements ProjectFilesystem {
this.sets.push(path);
return;
}
}
export class OverlayFilesystem implements ProjectFilesystem {
@@ -222,6 +222,13 @@ export class CodeProject {
while (m = re7.exec(text)) {
this.pushAllFiles(files, m[1]);
}
// for dialog -- %% #include "file" (dialog has no include directive,
// so the compiler is handed every file listed in a comment instead)
let re8 = /^\s*%%\s*#include\s+"(.+?)"/gm;
while (m = re8.exec(text)) {
this.pushAllFiles(files, m[1]);
}
// TODO: for xa assembler
}
return files;
}
@@ -240,7 +247,7 @@ export class CodeProject {
}
return files;
}
loadFileDependencies(text:string) : Promise<Dependency[]> {
let includes = this.parseIncludeDependencies(text);
let linkfiles = this.parseLinkDependencies(text);
@@ -442,7 +449,7 @@ export class CodeProject {
}
}
}
stripLocalPath(path : string) : string {
if (this.mainPath) {
var folder = getFolderForPath(this.mainPath);
+32 -32
View File
@@ -200,39 +200,39 @@ export class GithubService {
// load README
return sess.repo.contents('README.md').read();
})
.catch( (e) => {
console.log(e);
console.log('no README.md found')
// make user repo exists
return sess.repo.fetch().then( (_repo) => {
return ''; // empty README
.catch((e) => {
console.log(e);
console.log('no README.md found')
// make user repo exists
return sess.repo.fetch().then((_repo) => {
return ''; // empty README
})
})
})
.then( (readme) => {
var m;
// check README for main file
const re8main = /8bitworkshop.com[^)]+file=([^)&]+)/;
m = re8main.exec(readme);
if (m && m[1]) {
console.log("main path: '" + m[1] + "'");
sess.mainPath = m[1];
}
// check README for proper platform
// unless we use githubURL=
// TODO: cannot handle multiple URLs in README
const re8plat = /8bitworkshop.com[^)]+platform=([A-Za-z0-9._\-]+)/;
m = re8plat.exec(readme);
if (m) {
console.log("platform id: '" + m[1] + "'");
if (sess.platform_id && !sess.platform_id.startsWith(m[1]))
throw Error("Platform mismatch: Repository is " + m[1] + ", you have " + sess.platform_id + " selected.");
sess.platform_id = m[1];
}
// bind to repository
this.bind(sess, true);
// get head commit
return sess;
});
.then((readme) => {
var m;
// check README for main file
const re8main = /8bitworkshop.com[^)]+file=([^)&]+)/;
m = re8main.exec(readme);
if (m && m[1]) {
console.log("main path: '" + m[1] + "'");
sess.mainPath = m[1];
}
// check README for proper platform
// unless we use githubURL=
// TODO: cannot handle multiple URLs in README
const re8plat = /8bitworkshop.com[^)]+platform=([A-Za-z0-9._\-]+)/;
m = re8plat.exec(readme);
if (m) {
console.log("platform id: '" + m[1] + "'");
if (sess.platform_id && !sess.platform_id.startsWith(m[1]))
throw Error("Platform mismatch: Repository is '" + m[1] + "', you have '" + sess.platform_id + "' selected.");
sess.platform_id = m[1];
}
// bind to repository
this.bind(sess, true);
// get head commit
return sess;
});
}
pull(ghurl:string, deststore?) : Promise<GHSession> {
+9 -5
View File
@@ -43,7 +43,7 @@ export async function importProjectFromGithub(githuburl: string, replaceURL: boo
var sess: GHSession;
var urlparse = parseGithubURL(githuburl);
if (!urlparse) {
alertError('Could not parse Github URL.');
alertError('Could not parse GitHub URL.');
return;
}
// redirect to repo if exists
@@ -70,14 +70,14 @@ export async function importProjectFromGithub(githuburl: string, replaceURL: boo
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not import " + githuburl + "." + 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.");
alertInfo("You are signed in to GitHub.");
}).catch((e) => {
alertError("Could not sign in." + e);
});
@@ -86,7 +86,7 @@ export async function _loginToGithub(e) {
export async function _logoutOfGithub(e) {
var gh = await getGithubService();
gh.logout().then(() => {
alertInfo("You are logged out of Github.");
alertInfo("You are logged out of GitHub.");
});
}
@@ -94,6 +94,10 @@ export function _importProjectFromGithub(e) {
var modal = $("#importGithubModal");
var btn = $("#importGithubButton");
modal.modal('show');
modal.off('shown.bs.modal').on('shown.bs.modal', () => $("#importGithubURL").trigger('focus'));
$("#importGithubURL").off('keydown').on('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); btn.trigger('click'); }
});
btn.off('click').on('click', () => {
var githuburl = $("#importGithubURL").val() + "";
modal.modal('hide');
@@ -103,7 +107,7 @@ export function _importProjectFromGithub(e) {
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.)"))
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");
+9 -1
View File
@@ -113,6 +113,7 @@ export function getWorkerParams() {
const TOOL_TO_SOURCE_STYLE = {
'dasm': '6502',
'acme': '6502',
'xa': '6502',
'cc65': 'text/x-csrc',
'ca65': '6502',
'nesasm': '6502',
@@ -131,6 +132,7 @@ const TOOL_TO_SOURCE_STYLE = {
'yasm': 'gas',
'smlrc': 'text/x-csrc',
'inform6': 'inform6',
'dialog': 'dialog',
'fastbasic': 'fastbasic',
'basic': 'basic',
'silice': 'verilog',
@@ -160,6 +162,8 @@ const TOOL_TO_HELPURL = {
'cmoc': "http://perso.b2b2c.ca/~sarrazip/dev/cmoc.html",
'remote:llvm-mos': 'https://llvm-mos.org/wiki/Welcome',
'acme': 'https://raw.githubusercontent.com/sehugg/acme/main/docs/QuickRef.txt',
'xa': 'https://www.floodgap.com/retrotech/xa/',
'dialog': 'https://linusakesson.net/dialog/docs/',
}
function newWorker(): Worker {
@@ -489,7 +493,7 @@ function reloadProject(id: string) {
}
async function getSkeletonFile(fileid: string): Promise<string> {
var ext = platform.getToolForFilename(fileid);
var ext = platform.getToolForFilename(fileid).replace(/^remote:/, "");
try {
return await $.get("presets/" + getBasePlatform(platform_id) + "/skeleton." + ext, 'text');
} catch (e) {
@@ -1414,6 +1418,10 @@ function _addIncludeFile() {
addFileToProject("Include", ".ecs", (s) => { return 'import "' + s + '"' });
else if (tool == 'acme')
addFileToProject("Include", ".acme", (s) => { return '!src "' + s + '"' });
else if (tool == 'xa')
addFileToProject("Include", ".xa", (s) => { return '#include "' + s + '"' });
else if (tool == 'dialog')
addFileToProject("Include", ".dg", (s) => { return '%% #include "' + s + '"' });
else
alertError("Can't add include file to this project type (" + tool + ")");
}
+13
View File
@@ -1,4 +1,5 @@
import { DMG_PALETTE } from "../../common/gbpalette";
import { hex, rgb2bgr, safeident } from "../../common/util";
import { FileData } from "../../common/workertypes";
import * as pixed from "../pixeleditor";
@@ -80,6 +81,18 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
node = node.right;
}
});
// Game Boy: default to LCD green scale (0=light … 3=dark) when no project palette
if (result.length == 0 && platform_id && platform_id.startsWith('gb')) {
if (matchlen == 4) {
result.push({ node: null, name: "DMG Green", palette: new Uint32Array(DMG_PALETTE) });
} else if (matchlen == 2) {
result.push({
node: null,
name: "DMG Green",
palette: new Uint32Array([DMG_PALETTE[0], DMG_PALETTE[3]]),
});
}
}
return result;
}
+9 -4
View File
@@ -11,6 +11,7 @@ import { SourceFile, SourceLocation, WorkerError } from "../../common/workertype
import { asm6502 } from "../../parser/lang-6502";
import { basic } from "../../parser/lang-basic";
import { batariBasic } from "../../parser/lang-bataribasic";
import { dialog } from "../../parser/lang-dialog";
import { fastBasic } from "../../parser/lang-fastbasic";
import { inform6 } from "../../parser/lang-inform6";
import { verilog } from "../../parser/lang-verilog";
@@ -74,9 +75,10 @@ const MODEDEFS = {
gas: { isAsm: true },
vasm: { isAsm: true },
inform6: { theme: cobalt },
dialog: { theme: cobalt, lineWrap: true },
markdown: { lineWrap: true },
fastbasic: { noGutters: true },
basic: { noLineNumbers: true, noGutters: true },
basic: { noGutters: true },
ecs: { theme: mbo }, // TODO: is actually mixed-mode, as is verilog
}
@@ -129,7 +131,7 @@ export class SourceEditor implements ProjectView {
var isAsm = isAsmOverride || modedef.isAsm;
var lineWrap = !!modedef.lineWrap;
var theme = modedef.theme || MODEDEFS.default.theme;
var lineNums = !isAsm && !modedef.noLineNumbers && !isMobileDevice;
var lineNums = modedef.useLineNumbers && !isMobileDevice;
if (qs['embed']) {
lineNums = false; // no line numbers while embedded
isAsm = false; // no opcode bytes either
@@ -150,6 +152,9 @@ export class SourceEditor implements ProjectView {
case 'fastbasic':
parser = fastBasic();
break;
case 'dialog':
parser = dialog();
break;
case 'inform6':
parser = inform6();
break;
@@ -546,8 +551,8 @@ export class SourceEditor implements ProjectView {
effects: [
currentPcMarker.set.of(line.line),
currentPc.effect.of(line.line),
// Optional: follow the execution point
EditorView.scrollIntoView(this.editor.state.doc.line(line.line).from, { y: "center" }),
// Follow the execution point when stepping/hitting breakpoints.
...(moveCursor ? [EditorView.scrollIntoView(this.editor.state.doc.line(line.line).from, { y: "center" })] : []),
]
});
}
+1 -8
View File
@@ -2,16 +2,9 @@
import { SM83, SM83State } from "../common/cpu/SM83";
import { BasicScanlineMachine, Bus } from "../common/devices";
import { newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
import { DMG_PALETTE } from "../common/gbpalette";
import { hex } from "../common/util";
// Game Boy DMG palette: 4 shades of green (darkest to lightest)
const DMG_PALETTE: number[] = [
0xFFd0d884, // Lightest (Warm Mint)
0xFF87a84c, // Light Mid
0xFF526b34, // Dark Mid
0xFF2d3122, // Darkest (Deep Olive)
];
// Convert GBC 15-bit RGB (xBBBBBGGGGGRRRRR) to 32-bit ARGB
function cgbColorToARGB(lo: number, hi: number): number {
var r5 = lo & 0x1F;
+92
View File
@@ -0,0 +1,92 @@
// CodeMirror 6 language support for Dialog
// https://linusakesson.net/dialog/docs/
import { StreamLanguage, StreamParser } from "@codemirror/language";
import { LanguageSupport } from "@codemirror/language";
interface DialogState {
// parens are only nested inside a rule head/query; column 0 starts a rule
depth: number;
}
// TODO: Migrate to CodeMirror 6 Lezer parser.
function createDialogParser(): StreamParser<DialogState> {
// control keywords appear as the first word inside a parenthesis
const keywords = new Set([
"if", "then", "elseif", "else", "endif", "or", "select", "stopping",
"at random", "purely at random", "then purely at random", "cycling",
"collect", "into", "collect words", "determine object", "from words",
"matching all of", "stoppable", "stop", "just", "exhaust", "repeat forever",
"now", "global variable", "generate", "interface", "fail",
]);
function token(stream: any, state: DialogState): string | null {
if (stream.eatSpace()) return null;
const ch = stream.peek();
// %% comment to end of line
if (stream.match("%%")) {
stream.skipToEnd();
return "comment";
}
// \( and \) are escaped literals inside text
if (stream.match(/\\./)) {
return "escape";
}
if (ch == "(" || ch == ")") {
stream.next();
state.depth += ch == "(" ? 1 : -1;
if (state.depth < 0) state.depth = 0;
return "bracket";
}
if (ch == "[" || ch == "]" || ch == "{" || ch == "}") {
stream.next();
return "bracket";
}
// $Variable, or $ for an anonymous variable
if (stream.match(/\$[a-zA-Z0-9_]*/)) {
return "variableName";
}
// #object name
if (stream.match(/#[a-zA-Z0-9_+-]+/)) {
return "atom";
}
// @style class
if (stream.match(/@[a-zA-Z0-9_-]+/)) {
return "className";
}
if (stream.match(/-?\d+\b/)) {
return "number";
}
// ~ negation, * multi-query, | list tail
if (stream.match(/[~*|]/)) {
return "operator";
}
// words: keywords only count at the start of a predicate
if (stream.match(/[^\s()\[\]{}$#@~*|\\%]+/)) {
if (state.depth > 0 && keywords.has(stream.current().toLowerCase())) {
return "keyword";
}
// text outside of parentheses is printed verbatim by the story
return state.depth > 0 ? null : "string";
}
stream.next();
return null;
}
return {
startState(): DialogState {
return { depth: 0 };
},
token,
languageData: {
commentTokens: { line: "%%" }
}
};
}
/**
* Language support for Dialog
*/
export function dialog(): LanguageSupport {
return new LanguageSupport(StreamLanguage.define(createDialogParser()));
}
+1 -1
View File
@@ -49,7 +49,7 @@ class Apple2MAMEPlatform extends BaseMAME6502Platform implements Platform {
}
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getToolForFilename = getToolForFilename_6502;
getPresets() { return APPLE2_PRESETS; }
+1 -1
View File
@@ -83,7 +83,7 @@ abstract class Atari8MAMEPlatform extends BaseMAME6502Platform {
getPresets() { return Atari8_PRESETS; }
getToolForFilename = getToolForFilename_Atari8;
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtensions() { return [".c", ".bas", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".bas", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
showHelp = atari8_showHelp;
}
+1 -1
View File
@@ -86,7 +86,7 @@ abstract class C64MAMEPlatform extends BaseMAME6502Platform {
getPresets() { return C64_PRESETS; }
getToolForFilename = getToolForFilename_6502;
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
loadROM(title, data) {
if (!this.started) {
this.startModule(this.mainElement, {
+2
View File
@@ -7,6 +7,8 @@ const GB_PRESETS: Preset[] = [
{ id: 'hello.c', name: 'Hello World (C)' },
{ id: 'testdrawing.c', name: 'Drawing Routines (C)' },
{ id: 'testphys.c', name: 'Sprite Test (C)' },
{ id: 'chase.c', name: "Shiru's Chase Game" },
{ id: 'pakupaku.c', name: 'Paku Paku' },
{ id: 'hello.sgb', name: 'Hello World (ASM)' },
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
];
+1 -1
View File
@@ -561,7 +561,7 @@ class NESMAMEPlatform extends BaseMAME6502Platform implements Platform {
getPresets() { return JSNES_PRESETS; }
getToolForFilename = getToolForFilename_6502;
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
}
+1 -1
View File
@@ -49,7 +49,7 @@ class PCEnginePlatform implements Platform {
return getToolForFilename_6502(fn);
}
getDefaultExtensions() {
return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"];
return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"];
}
readAddress(addr: number): number {
return this.pce.Get(addr);
+3 -3
View File
@@ -303,7 +303,7 @@ class VCSPlatform extends BasePlatform {
return "\n" + dumpRAM(ram, 0x80, 0x80);
}
getToolForFilename = getToolForFilename_vcs;
getDefaultExtensions() { return [".dasm", ".a", ".bb", ".acme", ".ca65", ".cc2600", ".ecs", ".wiz", ".c"]; }
getDefaultExtensions() { return [".dasm", ".a", ".bb", ".acme", ".xa", ".ca65", ".cc2600", ".ecs", ".wiz", ".c"]; }
getROMExtension() { return ".a26"; }
getDebugCategories() {
@@ -480,7 +480,7 @@ class VCSMAMEPlatform extends BaseMAME6502Platform implements Platform {
getPresets = function() { return VCS_PRESETS; }
getToolForFilename = getToolForFilename_vcs;
getDefaultExtensions() { return [".c", ".bb", ".acme", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".bb", ".acme", ".xa", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
getROMExtension() { return ".a26"; }
getOriginPC = function() {
@@ -538,7 +538,7 @@ class VCSStellaPlatform implements Platform {
return this.running;
}
getToolForFilename = getToolForFilename_vcs;
getDefaultExtensions() { return [".c", ".bb", ".acme", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".bb", ".acme", ".xa", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
getROMExtension() { return ".a26"; }
getPresets() { return VCS_PRESETS }
}
+1 -1
View File
@@ -51,7 +51,7 @@ abstract class VIC20MAMEPlatform extends BaseMAME6502Platform {
getPresets() { return VIC20_PRESETS; }
getToolForFilename = getToolForFilename_6502;
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
loadROM(title, data) {
if (!this.started) {
this.startModule(this.mainElement, {
+5 -3
View File
@@ -6,7 +6,7 @@ import { InputResponse } from "../common/basic/runtime";
import { loadScript } from "../common/util";
const ZMACHINE_PRESETS = [
{ id: 'hello.inf', name: 'Hello World' },
{ id: 'hello.inf', name: 'Hello World', category: 'Inform 6' },
{ id: 'house01.inf', name: 'House Tutorial #1' },
{ id: 'house02.inf', name: 'House Tutorial #2' },
{ id: 'house03.inf', name: 'House Tutorial #3' },
@@ -25,6 +25,8 @@ const ZMACHINE_PRESETS = [
{ id: 'museum.inf', name: 'Museum of Inform' },
{ id: 'advent.inf', name: 'Colossal Cave Adventure' },
{ id: 'ztrek.inf', name: 'Super Z Trek' },
{ id: 'hello.dg', name: 'Hello Dialog', category: 'Dialog' },
{ id: 'cottage.dg', name: 'Cloak of Darkness' },
];
declare var ZVM;
@@ -704,10 +706,10 @@ class ZmachinePlatform implements Platform {
}
getToolForFilename(s: string): string {
return "inform6";
return s.endsWith(".dg") ? "dialog" : "inform6";
}
getDefaultExtensions() {
return [".inf"];
return [".inf", ".dg"];
}
showHelp() {
return "https://www.inform-fiction.org/manual/html/contents.html";
+10 -1
View File
@@ -49,6 +49,14 @@ export interface BuildStep extends WorkerBuildStep {
///
export function fixLineEndings(data: any): any {
if (typeof data === 'string') {
// C build tools require LF line endings.
return data.replace(/\r\n/g, '\n');
}
return data;
}
export class FileWorkingStore implements WorkingStore {
workfs: { [path: string]: FileEntry } = {};
workerseq: number = 0;
@@ -89,7 +97,7 @@ export class FileWorkingStore implements WorkingStore {
let data = this.getFileData(path);
if (data != null && typeof data !== 'string')
throw new Error(`${path}: expected string`)
return data as string; // TODO
return fixLineEndings(data) as string; // TODO
}
getFileEntry(path: string): FileEntry {
return this.workfs[path];
@@ -256,6 +264,7 @@ export function populateEntry(fs, path: string, entry: FileEntry, options: Build
if (options && options.processFn) {
data = options.processFn(path, data);
}
data = fixLineEndings(data);
// create subfolders
var toks = path.split('/');
if (toks.length > 1) {
Binary file not shown.
+71
View File
@@ -0,0 +1,71 @@
import { WorkerError } from "../../common/workertypes";
import { WASIFilesystem, WASIRunner } from "../../common/wasi/wasishim";
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, store, putWorkFile } from "../builder";
import { loadWASIFilesystemZip } from "../wasiutils";
import { loadWASMBinary } from "../wasmutils";
// the Dialog standard library ships in the preload filesystem, but a project
// can override it by including its own copy
const STDLIB = "stdlib.dg";
let dialog_fs: WASIFilesystem | null = null;
let wasiModule: WebAssembly.Module | null = null;
// Error: story.dg, line 6: Unexpected token at beginning of line.
// Error: No library (such as stdlib.dg) was specified on the commandline.
const re_error = /^Error:\s+(?:(\S+?), line (\d+):\s+)?(.+)/;
export async function compileDialog(step: BuildStep): Promise<BuildStepResult> {
const errors: WorkerError[] = [];
gatherFiles(step, { mainFilePath: "main.dg" });
const destpath = step.prefix + ".z8";
if (staleFiles(step, [destpath])) {
if (!dialog_fs) {
dialog_fs = await loadWASIFilesystemZip("dialog-fs.zip");
}
if (!wasiModule) {
wasiModule = new WebAssembly.Module(loadWASMBinary("dialogc"));
}
const wasi = new WASIRunner();
wasi.initSync(wasiModule);
wasi.fs.setParent(dialog_fs);
for (let file of step.files) {
wasi.fs.putFile("./" + file, store.getFileData(file));
}
wasi.addPreopenDirectory(".");
// sources are matched in command-line order, so the story comes first
// and the library last -- a project's own stdlib.dg shadows the
// preloaded one, since putFile() writes to the child filesystem
const sources = step.files.filter(fn => fn.endsWith(".dg") && fn != STDLIB);
sources.push(STDLIB);
wasi.setArgs(["dialogc", "-t", "z8", "-o", destpath, ...sources]);
try {
wasi.run();
} catch (e) {
errors.push({ line: 0, msg: e + "" });
}
const stderr = wasi.fds[2].getBytesAsString();
for (let line of stderr.split("\n")) {
const matches = re_error.exec(line);
if (matches) {
errors.push({
path: matches[1] || step.path,
line: parseInt(matches[2]) || 0,
msg: matches[3]
});
} else if (line) {
console.log(line); // warnings, progress messages
}
}
if (errors.length) {
return { errors };
}
const outfile = wasi.fs.getFile("./" + destpath);
if (!outfile) {
return { errors: [{ line: 0, msg: "dialogc did not produce " + destpath }] };
}
const output = outfile.getBytes();
putWorkFile(destpath, output);
return { output, errors };
}
}
+147
View File
@@ -0,0 +1,147 @@
import { WASIRunner } from "../../common/wasi/wasishim";
import { CodeListing, CodeListingMap } from "../../common/workertypes";
import { BuildStep, BuildStepResult, gatherFiles, putWorkFile, staleFiles, store } from "../builder";
import { re_crlf } from "../listingutils";
import { loadWASMBinary } from "../wasmutils";
// xa listing lines look like this:
// 5 A:c000 a9 05 start lda #$05 ; comment
// (line number, segment:address, then fixed-width byte / label / source columns)
const re_listing = /^\s*(\d+) (\w):([0-9a-f]+) (.*)$/i;
const LABEL_COL = 25; // width of the byte column
const SOURCE_COL = 35; // width of the byte + label columns
// xa writes errors to stderr, e.g.:
// main.xa:line 7: c00c:Overflow error
const re_error = /^(\S.*?):line (\d+): (?:[0-9a-f]+:)?\s*(.+)$/;
// the label file (-l) has one comma-separated entry per line:
// start, 0xc000, 0, 0x0000
const re_label = /^(\S+),\s*0x([0-9a-f]+)/i;
function parseXALabels(text: string) {
const symbolmap = {};
for (const line of text.split(re_crlf)) {
const m = re_label.exec(line);
if (m) {
symbolmap[m[1]] = parseInt(m[2], 16);
}
}
return symbolmap;
}
function parseXAListing(lsttext: string, listings: CodeListingMap) {
// xa prints the file name on its own line whenever it enters or resumes a file
let path: string | null = null;
let listing: CodeListing | null = null;
let lastline = 0;
const lastlines: { [path: string]: number } = {};
for (const line of lsttext.split(re_crlf)) {
const m = re_listing.exec(line);
if (!m) {
// a file name switches us to another listing
const newpath = line.trim();
if (listings[newpath]) {
if (path) lastlines[path] = lastline;
path = newpath;
listing = listings[path];
lastline = lastlines[path] || 0;
}
continue;
}
const linenum = parseInt(m[1]);
const offset = parseInt(m[3], 16);
const rest = m[4];
const insns = rest.substring(0, LABEL_COL).replace('...', '').trim();
const source = rest.substring(SOURCE_COL).trim();
// when xa resumes a file after an include it repeats its line counter
// for one line, so only accept line numbers that move forward
if (insns && listing && linenum > lastline) {
listing.lines.push({
line: linenum,
offset: offset,
insns: insns,
iscode: !source.startsWith('.'),
});
}
if (linenum > lastline) lastline = linenum;
}
}
// the origin is the address of the first byte written to the output file
function getListingOrigin(listings: CodeListingMap): number | undefined {
let origin: number | undefined;
for (const key in listings) {
for (const line of listings[key].lines) {
if (origin === undefined || line.offset < origin) origin = line.offset;
}
}
return origin;
}
let wasiModule: WebAssembly.Module | null = null;
export function assembleXA(step: BuildStep): BuildStepResult {
const errors = [];
gatherFiles(step, { mainFilePath: "main.xa" });
const binpath = step.prefix + ".bin";
const lstpath = step.prefix + ".lst";
const sympath = step.prefix + ".lbl";
if (staleFiles(step, [binpath])) {
if (!wasiModule) {
wasiModule = new WebAssembly.Module(loadWASMBinary("xa"));
}
const wasi = new WASIRunner();
wasi.initSync(wasiModule);
for (const file of step.files) {
wasi.fs.putFile("./" + file, store.getFileData(file));
}
wasi.addPreopenDirectory(".");
const args = ['xa', '-E', '-o', binpath, '-l', sympath, '-P', lstpath];
if (step.params?.xaargs) {
args.push.apply(args, step.params.xaargs);
}
args.push("-D__8BITWORKSHOP__=1");
if (step.mainfile) {
args.push("-D__MAIN__=1");
}
args.push(step.path);
wasi.setArgs(args);
try {
wasi.run();
} catch (e) {
errors.push({ line: 0, msg: e + "" });
}
const stderr = wasi.fds[2].getBytesAsString();
for (const line of stderr.split(re_crlf)) {
const m = re_error.exec(line);
if (m) {
errors.push({ path: m[1], line: parseInt(m[2]), msg: m[3] });
}
}
if (errors.length) {
return { errors };
}
const lstout = wasi.fs.getFile("./" + lstpath)?.getBytesAsString() || "";
const symout = wasi.fs.getFile("./" + sympath)?.getBytesAsString() || "";
const output = wasi.fs.getFile("./" + binpath)?.getBytes();
if (!output) {
return { errors: [{ line: 0, msg: "xa did not produce an output file" }] };
}
const listings: CodeListingMap = {};
for (const path of step.files) {
listings[path] = { lines: [] };
}
parseXAListing(lstout, listings);
putWorkFile(binpath, output);
putWorkFile(lstpath, lstout);
putWorkFile(sympath, symout);
return {
output,
errors,
listings,
symbolmap: parseXALabels(symout),
origin: getListingOrigin(listings),
};
}
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+4
View File
@@ -16,6 +16,8 @@ import * as cc7800 from './tools/cc7800'
import * as cc2600 from './tools/cc2600'
import * as bataribasic from './tools/bataribasic'
import * as oscar64 from './tools/oscar64'
import * as xa from './tools/xa'
import * as dialog from './tools/dialog'
import { PLATFORM_PARAMS } from "./platforms";
@@ -60,6 +62,8 @@ export const TOOLS = {
'armtcc': arm.compileARMTCC,
'armtcclink': arm.linkARMTCC,
'oscar64': oscar64.compileOscar64,
'xa': xa.assembleXA,
'dialog': dialog.compileDialog,
}
export const TOOL_PRELOADFS = {
+12 -12
View File
@@ -34,7 +34,7 @@ const t0 = new Date().getTime();
describe('Github', function() {
it('Should import from Github (check README)', function(done) {
it('Should import from GitHub (check README)', function(done) {
var store = createNewPersistentStore('vcs', function(store) {
var gh = newGH(store, 'vcs');
gh.importAndPull('https://github.com/pzpinfo/test123123/').then( (sess) => {
@@ -52,7 +52,7 @@ describe('Github', function() {
});
});
it('Should import from Github (default branch)', function(done) {
it('Should import from GitHub (default branch)', function(done) {
var store = createNewPersistentStore('nes', function(store) {
var gh = newGH(store, 'nes');
gh.importAndPull('https://github.com/sehugg/mdf2020-nes').then( (sess) => {
@@ -62,7 +62,7 @@ describe('Github', function() {
});
});
it('Should import from Github (explicit branch)', function(done) {
it('Should import from GitHub (explicit branch)', function(done) {
var store = createNewPersistentStore('nes', function(store) {
var gh = newGH(store, 'nes');
gh.importAndPull('https://github.com/sehugg/mdf2020-nes/tree/main').then( (sess) => {
@@ -72,7 +72,7 @@ describe('Github', function() {
});
});
it('Should import from Github (binary files)', function(done) {
it('Should import from GitHub (binary files)', function(done) {
var store = createNewPersistentStore('vcs', function(store) {
var gh = newGH(store, 'vcs');
gh.importAndPull('https://github.com/pzpinfo/testrepo3').then( (sess) => {
@@ -88,7 +88,7 @@ describe('Github', function() {
});
});
it('Should import from Github (wrong platform)', function(done) {
it('Should import from GitHub (wrong platform)', function(done) {
var store = createNewPersistentStore('_FOO', function(store) {
var gh = newGH(store, '_FOO');
gh.importAndPull('https://github.com/pzpinfo/testrepo1557326056720').catch( (e) => {
@@ -98,7 +98,7 @@ describe('Github', function() {
});
});
it('Should import from Github (invalid URL)', function(done) {
it('Should import from GitHub (invalid URL)', function(done) {
var store = createNewPersistentStore('_FOO', function(store) {
var gh = newGH(store, '_FOO');
gh.importAndPull('https://github.com/pzpinfo/NOEXISTSREPO').catch( (e) => {
@@ -109,7 +109,7 @@ describe('Github', function() {
});
});
it('Should import from Github (subdirectory tree)', function(done) {
it('Should import from GitHub (subdirectory tree)', function(done) {
var store = createNewPersistentStore('nes', function(store) {
var gh = newGH(store, 'nes');
gh.importAndPull('https://github.com/brovador/NESnake/tree/master/src').then( (sess) => {
@@ -120,7 +120,7 @@ describe('Github', function() {
});
});
it('Should publish (fail) on Github', function(done) {
it('Should publish (fail) on GitHub', function(done) {
var store = createNewPersistentStore(test_platform_id, function(store) {
var gh = newGH(store);
// should fail
@@ -130,7 +130,7 @@ describe('Github', function() {
});
});
it('Should publish new repository on Github', function(done) {
it('Should publish new repository on GitHub', function(done) {
var store = createNewPersistentStore(test_platform_id, function(store) {
var gh = newGH(store);
var reponame = 'testrepo'+t0;
@@ -144,7 +144,7 @@ describe('Github', function() {
});
});
it('Should commit/push to Github', function(done) {
it('Should commit/push to GitHub', function(done) {
var store = createNewPersistentStore(test_platform_id, function(store) {
var gh = newGH(store);
var binfile = new Uint8Array(256);
@@ -164,7 +164,7 @@ describe('Github', function() {
});
});
it('Should commit/push to Github (subdirectory tree)', function(done) {
it('Should commit/push to GitHub (subdirectory tree)', function(done) {
var store = createNewPersistentStore(test_platform_id, function(store) {
var gh = newGH(store);
var files = [
@@ -182,7 +182,7 @@ describe('Github', function() {
});
});
it('Should bind paths to Github', function(done) {
it('Should bind paths to GitHub', function(done) {
var store = createNewPersistentStore(test_platform_id, function(store) {
var gh = newGH(store);
var sess = {repopath:'foo/bar', url:'_', platform_id:'vcs',mainPath:'test.c'};
+39
View File
@@ -82,6 +82,45 @@ describe('test WASI cc7800', function () {
});
});
describe('test WASI dialogc', function () {
async function loadDialogc() {
let shim = await loadWASM('dialogc');
const zipdata = fs.readFileSync(`./src/worker/fs/dialog-fs.zip`);
shim.fs.setParent(await unzipWASIFilesystem(zipdata as any, "./"));
shim.addPreopenDirectory(".");
return shim;
}
it('dialogc compile', async function () {
let shim = await loadDialogc();
const src = fs.readFileSync('./presets/zmachine/hello.dg', 'utf8');
shim.fs.putFile("./hello.dg", src);
shim.setArgs(["dialogc", "-t", "z8", "-o", "hello.z8", "hello.dg", "stdlib.dg"]);
let errno = shim.run();
assert.strictEqual(shim.fds[2].getBytesAsString(), "");
assert.strictEqual(errno, 0);
const zfile = shim.fs.getFile("./hello.z8").getBytes();
assert.ok(zfile.length > 1000);
assert.strictEqual(zfile[0], 8); // z-machine version 8
});
it('dialogc syntax error', async function () {
let shim = await loadDialogc();
shim.fs.putFile("./bad.dg", "(room #room)\n(current player #player\n");
shim.setArgs(["dialogc", "-t", "z8", "-o", "bad.z8", "bad.dg", "stdlib.dg"]);
let errno = shim.run();
assert.strictEqual(errno, 1);
const stderr = shim.fds[2].getBytesAsString();
assert.ok(stderr.indexOf('Error: bad.dg, line ') >= 0, stderr);
});
it('dialogc missing source file', async function () {
let shim = await loadDialogc();
shim.setArgs(["dialogc", "-t", "z8", "-o", "x.z8", "nosuch.dg", "stdlib.dg"]);
let errno = shim.run();
assert.strictEqual(errno, 1);
const stderr = shim.fds[2].getBytesAsString();
assert.ok(stderr.indexOf('Error: Failed to open "nosuch.dg"') >= 0, stderr);
});
});
/*
describe('test WASI oscar64', function () {
it('oscar64 compile', async function () {
+1 -1
View File
@@ -22,7 +22,7 @@ try {
console.log('warning: need ./github.json with {token:"..."}')
}
exports['test import Github'] = async function (browser) {
exports['test import GitHub'] = async function (browser) {
await browser.url(`${IDEURL}?${QS_GITHUBURL}`)
.waitForElementNotPresent('#step-0')