Skip to content

Commit 9155c64

Browse files
committed
Update docs
1 parent a0957ae commit 9155c64

17 files changed

+274
-20
lines changed

.gitmodules

-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,3 @@
44
[submodule "deps/tauri-plugin-window-state"]
55
path = deps/tauri-plugin-window-state
66
url = https://github.com/mantou132/tauri-plugin-window-state.git
7-
[submodule "deps/wasm4"]
8-
path = deps/wasm4
9-
url = https://github.com/mantou132/wasm4.git

CONTRIBUTING.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@
2525

2626
```
2727
packages
28+
├── arcade 实现 NESBox 接口的街机模拟器
2829
├── config 共享配置
2930
├── e2e 端到端自动化测试
3031
├── ecs 简单 JS ECS 框架
3132
├── flutter_app Android/iOS App
3233
├── mt-app Android/iOS App 和 flutter_app 交互层
33-
├── nes 游戏模拟器的 WASM 接口,实现这个接口的任何模拟器都能在 NESBox 上运行
34+
├── nes
35+
│   ├── src 定义 NESBox 接口,实现 NESBox 接口的 NES 模拟器
36+
│   ├── utils 用于开发实现 NESBox 接口的 WASM 游戏
37+
│   └── utils_macro utils 中用到的宏
3438
├── nes-pkg 从 `nes` 构建自动生成的 ES 模块
39+
├── sandbox 运行 JavaScript 游戏的沙箱,实现了 NESBox 接口
3540
├── server NESBox GraphQL API
3641
│   ├── migrations 数据库 SQL
3742
│   └── src
@@ -50,6 +55,7 @@ packages
5055
│   │   ├── preload.rs 注入 js
5156
│   │   └── window_ext.rs 窗口自定义
5257
│ └─ ...
58+
├── wasm4 实现 NESBox 接口的 WASM4 运行时
5359
├── webapp Web, Desktop, Android/iOS 端都使用此包
5460
│   ├── src
5561
│   │   ├── elements 业务无关的自定义元素

README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<!-- ALL-CONTRIBUTORS-BADGE:END -->
88

9-
**NESBox's vision is to become the preferred platform for people playing online multiplayer retro games, providing an excellent user experience for all its users.**
9+
**NESBox's vision is to become the preferred platform for people playing online multiplayer games, providing an excellent user experience for all its users.**
1010

1111
> if there is copyright infringement, please contact to delete
1212
@@ -18,12 +18,20 @@
1818
- Support gamepad
1919
- Support pointer device(partial game)
2020
- Support game state restore
21-
- Support game cheat(only NES)
21+
- Support game cheat(partial game)
2222
- Support voice
2323
- Friend system, chat, invite
2424
- Internationalization
2525
- Run local game
2626

27+
## Support games
28+
29+
- [NES(FC)](https://en.wikipedia.org/wiki/Nintendo_Entertainment_System)
30+
- [Arcade](https://en.wikipedia.org/wiki/Arcade_video_game)
31+
- [Wasm4](https://wasm4.org/)
32+
- Universal Wasm (implementing [NESBox API](https://github.com/mantou132/nesbox/blob/dev/packages/nes/utils_macro/src/lib.rs))
33+
- Universal JavaScript (implementing [NESBox Sandbox API](https://github.com/mantou132/nesbox/blob/dev/packages/sandbox/types.d.ts))
34+
2735
## Download
2836

2937
- [Desktop](https://nesbox.xianqiao.wang)

deps/wasm4

-1
This file was deleted.

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "root",
44
"version": "0.0.1",
55
"scripts": {
6-
"lint": "lerna exec --ignore @nesbox/config --ignore @mantou/nes-wasm4 --ignore @mantou/nes -- tsc --noEmit && eslint . --ext .ts,.js",
6+
"lint": "lerna exec --ignore @mantou/nes -- tsc --noEmit && eslint . --ext .ts,.js",
77
"release": "lerna version",
88
"build:nes": "wasm-pack build --out-dir ../nes-pkg --target web --scope mantou packages/nes",
99
"publish:nes": "./scripts/nes-pkg.mjs",
@@ -31,7 +31,6 @@
3131
},
3232
"workspaces": [
3333
"games/*",
34-
"packages/*",
35-
"deps/wasm4/runtimes/web"
34+
"packages/*"
3635
]
3736
}

packages/config/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

packages/wasm4/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@mantou/wasm4",
3+
"version": "0.0.1",
4+
"description": "",
5+
"module": "src/index.ts",
6+
"types": "src/index.ts",
7+
"dependencies": {
8+
"@mantou/nes-wasm4": "^0.0.6"
9+
},
10+
"peerDependencies": {
11+
"@mantou/nes": "^1.0.13",
12+
"@mantou/nes-sandbox": "^0.0.12"
13+
},
14+
"devDependencies": {
15+
"@nesbox/config": "^0.0.1"
16+
}
17+
}

packages/wasm4/src/index.ts

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { Nes as ONes, Button, Player } from '@mantou/nes';
2+
import { encodeQoiFrame, decodeQoiFrame } from '@mantou/nes-sandbox';
3+
import {
4+
ADDR_GAMEPAD1,
5+
ADDR_MOUSE_BUTTONS,
6+
ADDR_MOUSE_X,
7+
ADDR_MOUSE_Y,
8+
BUTTON_DOWN,
9+
BUTTON_LEFT,
10+
BUTTON_RIGHT,
11+
BUTTON_UP,
12+
BUTTON_X,
13+
BUTTON_Z,
14+
HEIGHT,
15+
MOUSE_LEFT,
16+
MOUSE_RIGHT,
17+
PAUSE_REBOOTING,
18+
WIDTH,
19+
Runtime,
20+
} from '@mantou/nes-wasm4';
21+
22+
function getPlayerIdx(player: Player) {
23+
switch (player) {
24+
case Player.One:
25+
return 0;
26+
case Player.Two:
27+
return 1;
28+
case Player.Three:
29+
return 2;
30+
case Player.Four:
31+
return 3;
32+
}
33+
}
34+
35+
function getButtonBitFlag(button: Button) {
36+
switch (button) {
37+
case Button.JoypadA:
38+
return BUTTON_X;
39+
case Button.JoypadB:
40+
return BUTTON_Z;
41+
case Button.JoypadLeft:
42+
return BUTTON_LEFT;
43+
case Button.JoypadRight:
44+
return BUTTON_RIGHT;
45+
case Button.JoypadUp:
46+
return BUTTON_UP;
47+
case Button.JoypadDown:
48+
return BUTTON_DOWN;
49+
case Button.PointerPrimary:
50+
return MOUSE_LEFT;
51+
case Button.PointerSecondary:
52+
return MOUSE_RIGHT;
53+
default:
54+
return 0;
55+
}
56+
}
57+
58+
export class Wasm4 implements ONes {
59+
static new(_output_sample_rate: number): Wasm4 {
60+
return new Wasm4();
61+
}
62+
63+
#runtime = new Runtime('');
64+
65+
#frameNum = 0;
66+
#frameLen = WIDTH * HEIGHT * 4;
67+
#frameSpace = [0, this.#frameLen - 1];
68+
#qoiLen = this.#frameLen;
69+
#qoiSpace = [this.#frameSpace[1] + 1, this.#frameSpace[1] + this.#qoiLen];
70+
#deQoiLen = this.#frameLen;
71+
#deQoiSpace = [this.#qoiSpace[1] + 1, this.#qoiSpace[1] + this.#deQoiLen];
72+
#mem = new Uint8Array(this.#deQoiSpace[1] + 1);
73+
74+
#prevFrame = new Uint8ClampedArray();
75+
#currentQoiFrameLen = 0;
76+
#currentDeQoiLen = 0;
77+
#ready = false;
78+
#sound = false;
79+
80+
mem(): Uint8Array {
81+
return this.#mem;
82+
}
83+
width() {
84+
return WIDTH;
85+
}
86+
height() {
87+
return HEIGHT;
88+
}
89+
frame(qoi: boolean, qoiWholeFrame: boolean): number {
90+
if (qoi) {
91+
const currentFrame = new Uint8ClampedArray(this.#mem.buffer, this.#frameSpace[0], this.#frameLen);
92+
const [qoiFrame, partArr] = encodeQoiFrame(this.#prevFrame, currentFrame, this.width(), qoiWholeFrame);
93+
if (qoiFrame.length + partArr.length <= this.#qoiLen) {
94+
this.#currentQoiFrameLen = qoiFrame.length + partArr.length;
95+
new Uint8Array(this.#mem.buffer, this.#qoiSpace[0], this.#qoiLen).set(qoiFrame);
96+
new Uint8Array(this.#mem.buffer, this.#qoiSpace[0] + qoiFrame.byteLength, partArr.length).set(partArr);
97+
this.#prevFrame = currentFrame.slice(0);
98+
}
99+
} else {
100+
this.#prevFrame = new Uint8ClampedArray();
101+
}
102+
return this.#frameSpace[0];
103+
}
104+
frame_len(): number {
105+
return this.#frameLen;
106+
}
107+
qoi_frame(): number {
108+
return this.#qoiSpace[0];
109+
}
110+
qoi_frame_len(): number {
111+
return this.#currentQoiFrameLen;
112+
}
113+
decode_qoi(bytes: Uint8Array): number {
114+
const frame = decodeQoiFrame(bytes.buffer).data;
115+
this.#currentDeQoiLen = frame.length;
116+
new Uint8Array(this.#mem.buffer, this.#deQoiSpace[0], this.#deQoiLen).set(frame);
117+
return this.#deQoiSpace[0];
118+
}
119+
decode_qoi_len(): number {
120+
return this.#currentDeQoiLen;
121+
}
122+
async load_rom(bytes: Uint8Array) {
123+
if (!this.#ready) await this.#runtime.init();
124+
await this.#runtime.load(bytes);
125+
this.#runtime.start();
126+
}
127+
clock_frame(): number {
128+
this.#runtime.update();
129+
this.#runtime.composite();
130+
const { gl } = this.#runtime.compositor;
131+
const currentFrame = new Uint8ClampedArray(this.#mem.buffer, this.#frameSpace[0], this.#frameLen);
132+
gl.readPixels(0, 0, WIDTH, HEIGHT, this.#runtime.compositor.gl.RGBA, gl.UNSIGNED_BYTE, currentFrame);
133+
return this.#frameNum++;
134+
}
135+
audio_callback(out: Float32Array) {
136+
if (!this.#sound) return;
137+
const left = new Float32Array(out.length);
138+
const right = new Float32Array(out.length);
139+
this.#runtime.apu.process([[]], [[left, right]], {});
140+
for (let i = 0; i < out.length; i++) {
141+
out[i] = (left[i] + right[i]) / 2;
142+
}
143+
}
144+
handle_button_event(player: Player, button: Button, pressed: boolean) {
145+
if (button === Button.Start && pressed) {
146+
switch (this.#runtime.pauseState) {
147+
case 0:
148+
this.#runtime.pauseState = PAUSE_REBOOTING;
149+
return;
150+
case PAUSE_REBOOTING:
151+
this.#runtime.pauseState = 0;
152+
return;
153+
}
154+
}
155+
const x = this.#runtime.data.getInt16(ADDR_MOUSE_X, true);
156+
const y = this.#runtime.data.getInt16(ADDR_MOUSE_Y, true);
157+
const btn = getButtonBitFlag(button);
158+
const playerIdx = getPlayerIdx(player);
159+
const mouseButtons = this.#runtime.data.getUint8(ADDR_MOUSE_BUTTONS);
160+
const buttons = this.#runtime.data.getUint8(ADDR_GAMEPAD1 + playerIdx);
161+
162+
if (button === Button.PointerPrimary || button === Button.PointerSecondary) {
163+
this.#runtime.setMouse(x, y, pressed ? mouseButtons | btn : mouseButtons & ~btn);
164+
return;
165+
}
166+
this.#runtime.setGamepad(playerIdx, pressed ? buttons | btn : buttons & ~btn);
167+
}
168+
handle_motion_event(_player: Player, x: number, y: number, _dx: number, _dy: number) {
169+
this.#runtime.setMouse(x, y, 0);
170+
}
171+
state(): Uint8Array {
172+
return new Uint8Array(this.#runtime.memory.buffer);
173+
}
174+
load_state(state: Uint8Array) {
175+
new Uint8Array(this.#runtime.memory.buffer).set(state);
176+
}
177+
async reset() {
178+
const wasmBuffer = this.#runtime.wasmBuffer;
179+
if (wasmBuffer) {
180+
this.#runtime.reset(true);
181+
this.#runtime.pauseState = PAUSE_REBOOTING;
182+
await this.#runtime.load(wasmBuffer);
183+
this.#runtime.pauseState = 0;
184+
this.#runtime.start();
185+
}
186+
}
187+
set_sound(enabled: boolean) {
188+
this.#sound = enabled;
189+
}
190+
sound(): boolean {
191+
return this.#sound;
192+
}
193+
ram_map(): Uint32Array {
194+
const lastArea = this.#runtime.framebuffer.bytes;
195+
return new Uint32Array([lastArea.byteOffset + lastArea.length, 0xffff]);
196+
}
197+
ram(): Uint8Array {
198+
const lastArea = this.#runtime.framebuffer.bytes;
199+
return new Uint8Array(this.#runtime.data.buffer, lastArea.byteOffset + lastArea.length);
200+
}
201+
read_ram(addr: number): number {
202+
return this.#runtime.data.getUint8(addr);
203+
}
204+
write_ram(addr: number, val: number) {
205+
return this.#runtime.data.setUint8(addr, val);
206+
}
207+
/**
208+
* @ignore
209+
*/
210+
free() {
211+
// no body
212+
}
213+
}

packages/wasm4/tsconfig.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "@nesbox/config/tsconfig",
3+
"compilerOptions": {
4+
"baseUrl": "./"
5+
}
6+
}

packages/webapp/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@mantou/gem": "^1.6.0",
1717
"@mantou/nes": "^1.0.13",
1818
"@mantou/nes-sandbox": "^0.0.12",
19-
"@mantou/nes-wasm4": "^0.0.2",
19+
"@mantou/wasm4": "^0.0.1",
2020
"duoyun-ui": "^0.0.63",
2121
"graphql": "^16.2.0",
2222
"jszip": "^3.10.0",

packages/webapp/src/locales/en/homepage-message.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": {
3-
"message": "Bring Memories Back"
3+
"message": "Enjoy Games"
44
},
55
"desc": {
66
"message": "Find your favorite game and call your friends, no matter how far you are, you can enjoy the game immediately on the NESBox, save the progress, continue next time."

packages/webapp/src/locales/ja/homepage-message.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": {
3-
"message": "記憶を取り戻す"
3+
"message": "ゲームを楽しむ"
44
},
55
"desc": {
66
"message": "お気に入りのゲームを見つけて、友達に電話をかけましょう。遠く離れていても、すぐに NESBox でゲームを楽しみ、進行状況を保存して、次回に続きます。"

packages/webapp/src/locales/templates/homepage-message.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": {
3-
"message": "带回回忆"
3+
"message": "享受游戏"
44
},
55
"desc": {
66
"message": "找到你最喜欢的游戏叫上你的朋友,无论你们相距多远,都可以在 NESBox 上立即享受游戏,保存进度,下次继续"
@@ -15,7 +15,7 @@
1515
"message": "海量游戏任你选择"
1616
},
1717
"feature1Desc": {
18-
"message": "NESBox 收集了大量游戏。开始你喜欢的游戏后,Nesbox 会创建一个房间,你可以邀请朋友一起玩。"
18+
"message": "NESBox 收集了大量游戏。开始你喜欢的游戏后,NESBox 会创建一个房间,你可以邀请朋友一起玩。"
1919
},
2020
"feature2Title": {
2121
"message": "高品质的游戏体验"

packages/webapp/src/locales/zh-TW/homepage-message.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": {
3-
"message": "帶回回憶"
3+
"message": "享受遊戲"
44
},
55
"desc": {
66
"message": "找到你最喜歡的遊戲叫上你的朋友,無論你們相距多遠,都可以在 NESBox 上立即享受遊戲,保存進度,下次繼續"
@@ -15,7 +15,7 @@
1515
"message": "海量遊戲任你選擇"
1616
},
1717
"feature1Desc": {
18-
"message": "NESBox 收集了大量遊戲。開始你喜歡的遊戲後,Nesbox 會創建一個房間,你可以邀請朋友一起玩。"
18+
"message": "NESBox 收集了大量遊戲。開始你喜歡的遊戲後,NESBox 會創建一個房間,你可以邀請朋友一起玩。"
1919
},
2020
"feature2Title": {
2121
"message": "高品質的遊戲體驗"

packages/webapp/src/modules/stage.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export class MStageElement extends GemElement<State> {
296296
}
297297
} catch (err) {
298298
logger.error(err);
299-
Toast.open('error', 'ROM 加载错误');
299+
Toast.open('error', typeof err === 'string' ? err : 'ROM load fail');
300300
}
301301
this.#nextStartTime = 0;
302302
};

0 commit comments

Comments
 (0)