js-dos 7.4.9 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.github/workflows/build.yml +51 -0
  2. package/.gitmodules +4 -0
  3. package/README.deployment.md +11 -0
  4. package/README.md +29 -89
  5. package/index.html +106 -0
  6. package/package.json +46 -71
  7. package/postcss.config.cjs +8 -0
  8. package/src/app.css +14 -0
  9. package/src/base.css +10 -0
  10. package/src/components/checkbox.tsx +41 -0
  11. package/src/components/close-button.tsx +17 -0
  12. package/src/components/components.css +28 -0
  13. package/src/components/dos-option-checkbox.tsx +94 -0
  14. package/src/components/dos-option-select.tsx +108 -0
  15. package/src/components/dos-option-slider.tsx +35 -0
  16. package/src/components/error.tsx +16 -0
  17. package/src/components/loading.tsx +26 -0
  18. package/src/components/lock.tsx +10 -0
  19. package/src/components/select.tsx +33 -0
  20. package/src/components/slider.tsx +69 -0
  21. package/src/download-file.ts +18 -0
  22. package/src/frame/account-frame.tsx +156 -0
  23. package/src/frame/editor/defaults.ts +419 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +226 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +314 -0
  27. package/src/frame/fat-drives-frame.tsx +135 -0
  28. package/src/frame/frame.css +119 -0
  29. package/src/frame/frame.tsx +34 -0
  30. package/src/frame/image-rendering-frame.tsx +8 -0
  31. package/src/frame/network-frame.tsx +157 -0
  32. package/src/frame/quick-save-frame.tsx +96 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +87 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +35 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +179 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +132 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +197 -0
  45. package/src/public/types.ts +41 -0
  46. package/src/sidebar/account-button.tsx +33 -0
  47. package/src/sidebar/cloud-save-button.tsx +89 -0
  48. package/src/sidebar/diskette-icon.tsx +14 -0
  49. package/src/sidebar/fullscreen-button.tsx +44 -0
  50. package/src/sidebar/network-button.tsx +39 -0
  51. package/src/sidebar/sidebar-button.tsx +244 -0
  52. package/src/sidebar/sidebar.css +47 -0
  53. package/src/sidebar/sidebar.tsx +30 -0
  54. package/src/sockdrive/LICENSE +674 -0
  55. package/src/sockdrive/README.md +45 -0
  56. package/src/sockdrive/include/sockdrive.h +23 -0
  57. package/src/sockdrive/js/.eslintrc.json +43 -0
  58. package/src/sockdrive/js/package.json +29 -0
  59. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  60. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  61. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  62. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  63. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  64. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  65. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  66. package/src/sockdrive/js/src/sockdrive/drive.ts +457 -0
  67. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  68. package/src/sockdrive/js/src/sockdrive/types.ts +14 -0
  69. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  70. package/src/sockdrive/js/src/sockdrive-fat.ts +226 -0
  71. package/src/sockdrive/js/src/sockdrive-native.ts +100 -0
  72. package/src/sockdrive/js/src/test/test.ts +288 -0
  73. package/src/sockdrive/js/tsconfig.json +11 -0
  74. package/src/sockdrive/js/webpack.config.js +68 -0
  75. package/src/sockdrive/js/yarn.lock +1808 -0
  76. package/src/store/auth.ts +115 -0
  77. package/src/store/dos.ts +305 -0
  78. package/src/store/editor.ts +61 -0
  79. package/src/store/init.ts +48 -0
  80. package/src/store/storage.ts +32 -0
  81. package/src/store/ui.ts +186 -0
  82. package/src/store.tsx +108 -0
  83. package/src/ui.tsx +104 -0
  84. package/src/v8/changes.ts +56 -0
  85. package/src/v8/config.ts +18 -0
  86. package/src/v8/subscriptions.ts +62 -0
  87. package/src/vite-env.d.ts +2 -0
  88. package/src/window/dos/controls/keyboard.ts +32 -0
  89. package/src/window/dos/controls/keys.ts +331 -0
  90. package/src/window/dos/controls/mouse/mount.ts +99 -0
  91. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  92. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  93. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  94. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  95. package/src/window/dos/controls/mouse.ts +21 -0
  96. package/src/window/dos/dos-dhry2.tsx +191 -0
  97. package/src/window/dos/dos-runtime.tsx +214 -0
  98. package/src/window/dos/dos-window.tsx +84 -0
  99. package/src/window/dos/render/canvas.ts +65 -0
  100. package/src/window/dos/render/resize.ts +31 -0
  101. package/src/window/dos/render/webgl.ts +173 -0
  102. package/src/window/dos/sound/audio-node.ts +148 -0
  103. package/src/window/error-window.tsx +9 -0
  104. package/src/window/loading-window.tsx +54 -0
  105. package/src/window/prerun-window.tsx +123 -0
  106. package/src/window/select-window.tsx +118 -0
  107. package/src/window/window.css +78 -0
  108. package/src/window/window.tsx +49 -0
  109. package/src/ws/ws-sockdrive.ts +101 -0
  110. package/src/ws/ws-transport-layer.ts +455 -0
  111. package/tailwind.config.cjs +48 -0
  112. package/tsconfig.json +32 -0
  113. package/tsconfig.node.json +9 -0
  114. package/vite.config.ts +22 -0
  115. package/LICENSE +0 -339
  116. package/dist/emulators-ui-loader.png +0 -0
  117. package/dist/js-dos.css +0 -1690
  118. package/dist/js-dos.js +0 -17
  119. package/dist/js-dos.js.map +0 -1
  120. package/dist/types/src/backend/v7/latency.d.ts +0 -8
  121. package/dist/types/src/backend/v7/personal.d.ts +0 -2
  122. package/dist/types/src/backend/v7/v7-config.d.ts +0 -16
  123. package/dist/types/src/components/action-bar.d.ts +0 -2
  124. package/dist/types/src/components/action-hide.d.ts +0 -4
  125. package/dist/types/src/components/action-save-or-exit.d.ts +0 -4
  126. package/dist/types/src/components/client.d.ts +0 -6
  127. package/dist/types/src/components/controls.d.ts +0 -8
  128. package/dist/types/src/components/cpu-control.d.ts +0 -2
  129. package/dist/types/src/components/horizontal-slider.d.ts +0 -13
  130. package/dist/types/src/components/region.d.ts +0 -6
  131. package/dist/types/src/components/scale-control.d.ts +0 -3
  132. package/dist/types/src/components/sensitivity-control.d.ts +0 -3
  133. package/dist/types/src/components/sidebar/latency-info.d.ts +0 -7
  134. package/dist/types/src/components/sidebar/main.d.ts +0 -2
  135. package/dist/types/src/components/sidebar/networking.d.ts +0 -2
  136. package/dist/types/src/components/sidebar/token/token-add-time.d.ts +0 -2
  137. package/dist/types/src/components/sidebar/token/token-select.d.ts +0 -2
  138. package/dist/types/src/components/sidebar/token/token.d.ts +0 -17
  139. package/dist/types/src/components/sidebar.d.ts +0 -2
  140. package/dist/types/src/components/sync-control.d.ts +0 -2
  141. package/dist/types/src/components/tip.d.ts +0 -2
  142. package/dist/types/src/components/vertical-slider.d.ts +0 -12
  143. package/dist/types/src/components/volume-control.d.ts +0 -3
  144. package/dist/types/src/dom.d.ts +0 -8
  145. package/dist/types/src/hardware-transport-layer.d.ts +0 -18
  146. package/dist/types/src/icons.d.ts +0 -95
  147. package/dist/types/src/player-app.d.ts +0 -50
  148. package/dist/types/src/player.d.ts +0 -29
  149. package/dist/types/src/request.d.ts +0 -1
  150. package/dist/types/src/xhr.d.ts +0 -6
  151. package/dist/types/v7-services/src/personal.d.ts +0 -3
  152. package/dist/wdosbox.js +0 -55
  153. package/dist/wdosbox.js.symbols +0 -3174
  154. package/dist/wdosbox.wasm +0 -0
  155. package/examples/digger/index.html +0 -71
@@ -0,0 +1,78 @@
1
+ .jsdos-rso {
2
+ .window {
3
+ @apply overflow-hidden;
4
+
5
+ .background-image {
6
+ @apply absolute left-12 right-0 h-full pointer-events-none;
7
+ background-position: center;
8
+ background-size: cover;
9
+ background-repeat: no-repeat;
10
+ &::after {
11
+ position: relative;
12
+ content: "";
13
+ display: block;
14
+ width: 100%;
15
+ height: 100%;
16
+ background-color: hsl(var(--b1)/var(--tw-bg-opacity));
17
+ opacity: 0.75;
18
+ }
19
+ }
20
+
21
+ .play-button {
22
+ &:hover {
23
+ color: hsl(var(--af));
24
+ }
25
+ }
26
+
27
+ .dhry2-window {
28
+ @apply absolute left-0 top-0 w-full h-full flex flex-col items-center justify-center;
29
+ @apply bg-black bg-opacity-80 text-2xl px-8 py-4 text-white;
30
+
31
+ .title {
32
+ @apply mb-4 text-center text-4xl;
33
+ }
34
+
35
+ .backend {
36
+ @apply mb-8 text-center;
37
+ }
38
+
39
+ .results {
40
+ @apply grid grid-cols-2 gap-4;
41
+
42
+ div:nth-child(even) {
43
+ @apply text-green-300;
44
+
45
+ span {
46
+ @apply text-white;
47
+ }
48
+ }
49
+
50
+ div:nth-child(2),
51
+ div:last-child {
52
+ @apply text-yellow-300;
53
+ }
54
+
55
+ }
56
+ }
57
+
58
+ .pre-run-window {
59
+ @apply overflow-x-hidden overflow-y-auto flex-grow flex flex-col items-center justify-center px-8 m-auto;
60
+
61
+ .slider {
62
+ @apply justify-center w-80 max-w-none;
63
+
64
+ .label {
65
+ @apply flex-grow;
66
+ }
67
+
68
+ .touch {
69
+ @apply w-40 flex-grow-0;
70
+ }
71
+ }
72
+ }
73
+
74
+ .select-window {
75
+ @apply m-auto;
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,49 @@
1
+ import { useSelector } from "react-redux";
2
+ import { State } from "../store";
3
+ import { DosWindow } from "./dos/dos-window";
4
+ import { ErrorWindow } from "./error-window";
5
+ import { LoadingWindow } from "./loading-window";
6
+ import { PreRunWindow } from "./prerun-window";
7
+ import { SelectWindow } from "./select-window";
8
+
9
+ export function Window(props: {}) {
10
+ const frameOpened = useSelector((state: State) => state.ui.frame) !== "none";
11
+ const frameXs = useSelector((state: State) => state.ui.frameXs);
12
+ const window = useSelector((state: State) => state.ui.window);
13
+ const background = useSelector((state: State) => state.ui.background);
14
+
15
+ let windowComponent = <Loading />;
16
+ switch (window) {
17
+ case "error": {
18
+ windowComponent = <ErrorWindow />;
19
+ } break;
20
+ case "loading": {
21
+ windowComponent = <LoadingWindow />;
22
+ } break;
23
+ case "prerun": {
24
+ windowComponent = <PreRunWindow />;
25
+ } break;
26
+ case "run": {
27
+ windowComponent = <DosWindow />;
28
+ } break;
29
+ case "select": {
30
+ windowComponent = <SelectWindow />;
31
+ } break;
32
+ default: ;
33
+ };
34
+
35
+ return <div class="window absolute w-full h-full bg-base-100">
36
+ <div class="background-image"
37
+ style={{ backgroundImage: (background ? "url(" + background + ")" : undefined ) }} />
38
+ <div class="relative flex flex-row h-full w-full items-start justify-start overflow-auto">
39
+ <div class={(frameOpened ? (frameXs ? "w-24" : "w-96") : "w-12") + " flex-shrink-0"}></div>
40
+ {windowComponent}
41
+ </div>
42
+ </div>;
43
+ }
44
+
45
+ function Loading() {
46
+ return <div class="w-full h-full flex justify-center items-center">
47
+ <div class="radial-progress animate-spin" style="--value:70;"></div>
48
+ </div>;
49
+ }
@@ -0,0 +1,101 @@
1
+ import { Handle, Stats } from "../sockdrive/js/src/sockdrive/types";
2
+ import { Drive } from "../sockdrive/js/src/sockdrive/drive";
3
+
4
+ interface Template {
5
+ name: string,
6
+ size: number,
7
+ heads: number,
8
+ cylinders: number,
9
+ sectors: number,
10
+ sectorSize: number,
11
+ }
12
+
13
+ export interface ReadResponse {
14
+ buffer?: Uint8Array,
15
+ code: number,
16
+ }
17
+
18
+ export function createSockdrive(
19
+ onOpen: (drive: string, read: boolean, write: boolean) => void,
20
+ onError: (e: Error) => void) {
21
+ let seq = 0;
22
+ const mapping: { [handle: Handle]: Drive } = {};
23
+ const templates: { [handle: number]: Template } = {};
24
+ const memory: { [handle: number]: Uint8Array } = {};
25
+ const stats: Stats = {
26
+ read: 0,
27
+ write: 0,
28
+ readTotalTime: 0,
29
+ cacheHit: 0,
30
+ cacheMiss: 0,
31
+ cacheUsed: 0,
32
+ };
33
+ const sockdrive = {
34
+ stats,
35
+ open: async (url: string, owner: string, name: string, token: string, aheadRange: number): Promise<Handle> => {
36
+ const response = await fetch(url.replace("wss://", "https://")
37
+ .replace("ws://", "http://") + "/template/" + owner + "/" + name);
38
+ const template = await response.json();
39
+ if (template.error) {
40
+ throw new Error(template.error);
41
+ }
42
+ seq++;
43
+ templates[seq] = {
44
+ name: template.name,
45
+ size: template.size ?? 0,
46
+ heads: template.heads ?? 1,
47
+ cylinders: template.cylinders ?? 520,
48
+ sectors: template.sectors ?? 63,
49
+ sectorSize: template.sector_size ?? 512,
50
+ };
51
+
52
+ const sectorSize = templates[seq].sectorSize;
53
+ memory[seq] = new Uint8Array(sectorSize + sectorSize * aheadRange);
54
+ mapping[seq] = new Drive(url, owner, name, token, stats, { HEAPU8: memory[seq] });
55
+ mapping[seq].onOpen((read, write) => {
56
+ onOpen(owner + "/" + name, read, write);
57
+ });
58
+ mapping[seq].onError(onError);
59
+ return seq;
60
+ },
61
+ read: (handle: Handle, sector: number): Promise<ReadResponse> => {
62
+ if (mapping[handle]) {
63
+ return (mapping[handle].read(sector, 0, false) as Promise<number>).then((code) => {
64
+ return {
65
+ code,
66
+ buffer: memory[handle].slice(templates[seq].sectorSize),
67
+ };
68
+ });
69
+ }
70
+
71
+ console.error("ERROR! sockdrive handle", handle, "not found");
72
+ return Promise.resolve({ code: 1 });
73
+ },
74
+ write: (handle: Handle, sector: number, buffer: Uint8Array): number => {
75
+ if (buffer.byteLength != templates[handle].sectorSize) {
76
+ onError(new Error("sockdrive write buffer size " + buffer.byteLength +
77
+ " != sector size " + templates[handle].sectorSize));
78
+ return 1;
79
+ }
80
+ if (mapping[handle]) {
81
+ memory[handle].set(buffer, 0);
82
+ return mapping[handle].write(sector, 0);
83
+ }
84
+ console.error("ERROR! sockdrive handle", handle, "not found");
85
+ return 1;
86
+ },
87
+ close: (handle: Handle) => {
88
+ if (mapping[handle]) {
89
+ mapping[handle].close();
90
+ delete memory[handle];
91
+ delete templates[handle];
92
+ delete mapping[handle];
93
+ }
94
+ },
95
+ template: (handle: Handle) => {
96
+ return templates[handle];
97
+ },
98
+ };
99
+
100
+ return sockdrive;
101
+ }
@@ -0,0 +1,455 @@
1
+ import {
2
+ TransportLayer, ClientMessage, MessageHandler,
3
+ ServerMessage, DataChunk, FsNode,
4
+ } from "emulators/dist/types/protocol/protocol";
5
+
6
+ import { createSockdrive } from "./ws-sockdrive";
7
+
8
+ const sockdriveConfirmWrite = false;
9
+
10
+ export interface Hardware {
11
+ readConfig(): string;
12
+ sendMessage(payload: string): void;
13
+ addKey(key: number, pressed: number, timeMs: number): void;
14
+ mouseMove(x: number, y: number, relative: boolean, timeMs: number): void;
15
+ mouseButton(button: number, pressed: number, timeMs: number): void;
16
+ getFramePayload(): string;
17
+
18
+ createFile(path: string): string;
19
+ appendFile(blob: string): string;
20
+ writeFile(path: string): string;
21
+ closeFile(): string;
22
+ }
23
+
24
+ const textDecoder = new TextDecoder();
25
+ const textEncoder = new TextEncoder();
26
+ const fMultiplier = 200000000;
27
+
28
+ // eslint-disable-next-line max-len
29
+ const clientMessageValues: ClientMessage[] = [
30
+ "wc-install", "wc-run", "wc-pack-fs-to-bundle", "wc-add-key", "wc-mouse-move", "wc-mouse-button", "wc-mouse-sync",
31
+ "wc-exit", "wc-sync-sleep", "wc-pause", "wc-resume", "wc-mute", "wc-unmute", "wc-connect", "wc-disconnect",
32
+ "wc-backend-event", "wc-asyncify-stats", "wc-fs-tree", "wc-fs-get-file", "wc-send-data-chunk",
33
+ ];
34
+ const clientMessageEnum: { [msg: string]: number } = {};
35
+ clientMessageValues.forEach((v, i) => clientMessageEnum[v] = i);
36
+
37
+ const serverMessageValues: ServerMessage[] = [
38
+ "ws-extract-progress", "ws-ready", "ws-server-ready", "ws-frame-set-size",
39
+ "ws-update-lines", "ws-log", "ws-warn", "ws-err",
40
+ "ws-stdout", "ws-exit", "ws-persist", "ws-sound-init", "ws-sound-push",
41
+ "ws-config", "ws-sync-sleep", "ws-connected", "ws-disconnected",
42
+ "ws-asyncify-stats", "ws-fs-tree", "ws-send-data-chunk",
43
+ ];
44
+ const serverMessageEnum: { [num: string]: ServerMessage } = {};
45
+ serverMessageValues.forEach((v, i) => serverMessageEnum[i] = v);
46
+
47
+ export class WsTransportLayer implements TransportLayer {
48
+ socket: WebSocket;
49
+ sessionId: string = Date.now() + "";
50
+ hardware: Hardware;
51
+
52
+ private cycles = 0;
53
+ private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this), this.onSockdriveError.bind(this));
54
+
55
+ private handler: MessageHandler = () => {/**/};
56
+
57
+ private writeUint32(container: Uint8Array, value: number, offset: number) {
58
+ container[offset] = value & 0xFF;
59
+ container[offset + 1] = (value & 0x0000FF00) >> 8;
60
+ container[offset + 2] = (value & 0x00FF0000) >> 16;
61
+ container[offset + 3] = (value & 0xFF000000) >> 24;
62
+ return offset + 4;
63
+ }
64
+
65
+ private readUint32(container: Uint8Array, offset: number) {
66
+ return (container[offset] & 0x000000FF) |
67
+ ((container[offset + 1] << 8) & 0x0000FF00) |
68
+ ((container[offset + 2] << 16) & 0x00FF0000) |
69
+ ((container[offset + 3] << 24) & 0xFF000000);
70
+ }
71
+
72
+ private readUint64(container: Uint8Array, offset: number) {
73
+ return this.readUint32(container, offset) + this.readUint32(container, offset + 4) * 2 ** 32;
74
+ }
75
+
76
+ private sendMessageToSocket(id: ClientMessage | number, ...payload: (Uint8Array | null)[]) {
77
+ let length = 1;
78
+ for (const next of payload) {
79
+ length += 4 + (next?.length ?? 0);
80
+ }
81
+
82
+ const encoded = new Uint8Array(length);
83
+ encoded[0] = typeof id === "string" ? clientMessageEnum[id] : id;
84
+ let position = 1;
85
+ for (const next of payload) {
86
+ this.writeUint32(encoded, next?.length ?? 0, position);
87
+ position += 4;
88
+ if (next) {
89
+ encoded.set(next, position);
90
+ position += next.length;
91
+ }
92
+ }
93
+
94
+ this.socket.send(encoded);
95
+ }
96
+
97
+ private readMessage(payload: Uint8Array): { id: number, payload: (Uint8Array | null)[] } {
98
+ const out: Uint8Array[] = [];
99
+ let position = 1;
100
+ while (position + 4 <= payload.length) {
101
+ const length = this.readUint32(payload, position);
102
+ position += 4;
103
+
104
+ if (position + length > payload.length) {
105
+ throw new Error("read_message out of bounds");
106
+ } else {
107
+ out.push(payload.slice(position, position + length));
108
+ }
109
+
110
+ position += length;
111
+ }
112
+
113
+ return {
114
+ id: payload[0],
115
+ payload: out,
116
+ };
117
+ }
118
+
119
+ private onMessage(msg: Uint8Array) {
120
+ const { id, payload } = this.readMessage(msg);
121
+ const message = serverMessageEnum[id];
122
+ switch (message) {
123
+ case "ws-ready":
124
+ case "ws-server-ready":
125
+ case "ws-exit":
126
+ {
127
+ // delay ws-server-ready until ws-sound-init
128
+ if (message !== "ws-server-ready") {
129
+ this.handler(message, {});
130
+ }
131
+ } break;
132
+ case "ws-stdout":
133
+ case "ws-log":
134
+ case "ws-warn":
135
+ case "ws-err": {
136
+ this.handler(message, {
137
+ tag: textDecoder.decode(payload[0]!),
138
+ message: textDecoder.decode(payload[1]!),
139
+ });
140
+ } break;
141
+ case "ws-send-data-chunk": {
142
+ const chunk: DataChunk = {
143
+ type: textDecoder.decode(payload[0]!) as any,
144
+ name: textDecoder.decode(payload[1]!),
145
+ data: payload[2],
146
+ };
147
+ this.handler(message, { chunk });
148
+ } break;
149
+ case "ws-config": {
150
+ this.handler(message, {
151
+ dosboxConf: payload[0]!,
152
+ jsdosConf: textDecoder.decode(payload[1]!),
153
+ });
154
+ } break;
155
+ case "ws-sound-init": {
156
+ this.handler(message, {
157
+ freq: this.readUint32(payload[0]!, 0),
158
+ });
159
+ this.handler("ws-server-ready", {});
160
+ } break;
161
+ case "ws-sound-push": {
162
+ this.handler(message, {
163
+ samples: new Float32Array(payload[0]!.buffer),
164
+ });
165
+ } break;
166
+ case "ws-frame-set-size": {
167
+ this.handler(message, {
168
+ width: this.readUint32(payload[0]!, 0),
169
+ height: this.readUint32(payload[0]!, 4),
170
+ });
171
+ } break;
172
+ case "ws-update-lines": {
173
+ if (payload.length > 0) {
174
+ const lines: { start: number, heapu8: Uint8Array }[] = [];
175
+ for (const next of payload) {
176
+ lines.push({
177
+ start: this.readUint32(next!, 0),
178
+ heapu8: next!.slice(4),
179
+ });
180
+ }
181
+ this.handler(message, { lines });
182
+ }
183
+ } break;
184
+ case "ws-asyncify-stats": {
185
+ this.cycles += this.readUint32(payload[0]!, 0);
186
+ const stats = {
187
+ messageSent: this.readUint32(payload[0]!, 4),
188
+ messageReceived: this.readUint32(payload[0]!, 8),
189
+ messageFrame: this.readUint32(payload[0]!, 12),
190
+ messageSound: this.readUint32(payload[0]!, 16),
191
+ nonSkippableSleepCount: 0,
192
+ sleepCount: 0,
193
+ sleepTime: 0,
194
+ cycles: this.cycles,
195
+ netSent: 0,
196
+ netRecv: 0,
197
+ driveSent: this.sockdrive.stats.write,
198
+ driveRecv: this.sockdrive.stats.read,
199
+ driveRecvTime: this.sockdrive.stats.readTotalTime,
200
+ driveCacheHit: this.sockdrive.stats.cacheHit,
201
+ driveCacheMiss: this.sockdrive.stats.cacheMiss,
202
+ driveCacheUsed: this.sockdrive.stats.cacheUsed,
203
+ };
204
+ this.handler(message, stats);
205
+ } break;
206
+ case "ws-connected": {
207
+ this.handler(message, { networkType: payload[0]![0], address: textDecoder.decode(payload[1]!) });
208
+ } break;
209
+ case "ws-disconnected": {
210
+ this.handler(message, { networkType: payload[0]![0] });
211
+ } break;
212
+ case "ws-fs-tree": {
213
+ const info: { name: string, size: number | null }[] = [];
214
+ for (let i = 0; i < payload.length - 1; ++i) {
215
+ info.push({ name: textDecoder.decode(payload[i]!), size: null });
216
+ }
217
+
218
+ const sizes = payload[payload.length - 1]!;
219
+ for (let i = 0; i < info.length; ++i) {
220
+ const size = this.readUint32(sizes, i * 4);
221
+ info[i].size = size < 0 ? null : size;
222
+ }
223
+
224
+ const fsTree: FsNode = {
225
+ name: ".",
226
+ nodes: [],
227
+ size: null,
228
+ };
229
+
230
+ function lookupNode(name: string): [FsNode, string] {
231
+ const parts = name.split("/");
232
+ let node: FsNode | undefined = fsTree;
233
+ for (let i = 1; i < parts.length - 1; ++i) {
234
+ const dir = parts[i];
235
+ node = node!.nodes?.find((n) => n.name == dir);
236
+ if (!node) {
237
+ throw new Error("Unable to find node " + name + ", subdir " + dir);
238
+ }
239
+ }
240
+ return [node, parts[parts.length - 1]];
241
+ };
242
+
243
+ for (const { name, size } of info) {
244
+ const [node, basename] = lookupNode(name);
245
+ node.nodes?.push({
246
+ name: basename,
247
+ size,
248
+ nodes: size === null ? [] : null,
249
+ });
250
+ }
251
+
252
+ this.handler("ws-fs-tree", {
253
+ fsTree,
254
+ });
255
+ } break;
256
+ case "ws-persist": {
257
+ this.handler("ws-persist", {
258
+ bundle: payload.length > 0 ? payload[0]! : null,
259
+ });
260
+ } break;
261
+ default: {
262
+ if (message === undefined) { // not standard messages
263
+ (async () => {
264
+ switch (id) {
265
+ case 100/* ws-sockdrive-open */: {
266
+ const url = textDecoder.decode(payload[0]!);
267
+ const owner = textDecoder.decode(payload[1]!);
268
+ const name = textDecoder.decode(payload[2]!);
269
+ const token = textDecoder.decode(payload[3]!);
270
+ const aheadRange = this.readUint32(payload[4]!, 0);
271
+ const handle = await this.sockdrive.open(url, owner, name, token, aheadRange);
272
+ const template = this.sockdrive.template(handle);
273
+ const packet = new Uint8Array(4 * 6);
274
+ let offset = 0;
275
+ offset = this.writeUint32(packet, handle, offset);
276
+ offset = this.writeUint32(packet, template.size, offset);
277
+ offset = this.writeUint32(packet, template.heads, offset);
278
+ offset = this.writeUint32(packet, template.cylinders, offset);
279
+ offset = this.writeUint32(packet, template.sectors, offset);
280
+ this.writeUint32(packet, template.sectorSize, offset);
281
+ this.sendMessageToSocket(100, packet);
282
+ } break;
283
+ case 101/* ws-sockdrive-read */: {
284
+ const handle = this.readUint32(payload[0]!, 0);
285
+ const sector = this.readUint32(payload[0]!, 4);
286
+ const response = await this.sockdrive.read(handle, sector);
287
+ const packet = new Uint8Array(4);
288
+ this.writeUint32(packet, response.code, 0);
289
+ this.sendMessageToSocket(101, packet, response.buffer ?? null);
290
+ } break;
291
+ case 102/* ws-sockdrive-write */: {
292
+ const handle = this.readUint32(payload[0]!, 0);
293
+ const sector = this.readUint32(payload[0]!, 4);
294
+ const code = this.sockdrive.write(handle, sector, payload[1]!);
295
+ const packet = new Uint8Array(4);
296
+ this.writeUint32(packet, code, 0);
297
+ if (sockdriveConfirmWrite) {
298
+ this.sendMessageToSocket(102, packet);
299
+ }
300
+ } break;
301
+ case 103/* ws-sockdrive-close */: {
302
+ this.sockdrive.close(this.readUint32(payload[0]!, 0));
303
+ } break;
304
+ default:
305
+ console.log("WARN! Unhandled server non standard message", id, payload);
306
+ }
307
+ })().catch((e) => {
308
+ this.handler("ws-err", {
309
+ tag: "panic",
310
+ message: "sockdrive error: " + (e.message ?? "???"),
311
+ });
312
+ });
313
+ } else {
314
+ console.warn("WARN! Unhandled server message", message);
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ constructor(socket: WebSocket) {
321
+ this.socket = socket;
322
+ this.socket.addEventListener("error", (e) => {
323
+ this.handler("ws-err", { tag: "ws", message: (e as any).message ?? "Unknown transport layer error" });
324
+ this.handler("ws-exit", {});
325
+ this.socket.close();
326
+ });
327
+ this.socket.addEventListener("message", async (ev) => {
328
+ const blob: Blob = ev.data;
329
+ this.onMessage(new Uint8Array(await blob.arrayBuffer()));
330
+ });
331
+ this.sendMessageToSocket("wc-install");
332
+ this.hardware = (null) as any;
333
+ }
334
+
335
+ async sendMessageToServer(name: ClientMessage, props?: { [key: string]: any; }) {
336
+ if (props === undefined || props?.sessionId !== this.sessionId) {
337
+ return;
338
+ }
339
+
340
+ const messageId = clientMessageEnum[name];
341
+ switch (name) {
342
+ case "wc-run": {
343
+ const token = props.token ?? "";
344
+ this.sendMessageToSocket(messageId, textEncoder.encode(token));
345
+ } break;
346
+ case "wc-send-data-chunk": {
347
+ const chunk: DataChunk = props.chunk;
348
+ this.sendMessageToSocket(messageId,
349
+ textEncoder.encode(chunk.type),
350
+ textEncoder.encode(chunk.name),
351
+ chunk.data ? new Uint8Array(chunk.data) : null);
352
+ } break;
353
+ case "wc-asyncify-stats": {
354
+ this.sendMessageToSocket(messageId);
355
+ } break;
356
+ case "wc-add-key": {
357
+ const payload = new Uint8Array(3 * 4);
358
+ let offset = 0;
359
+ offset = this.writeUint32(payload, props.key, offset);
360
+ offset = this.writeUint32(payload, props.pressed ? 1 : 0, offset);
361
+ this.writeUint32(payload, props.timeMs, offset);
362
+ this.sendMessageToSocket(messageId, payload);
363
+ } break;
364
+ case "wc-mouse-move": {
365
+ const payload = new Uint8Array(3 * 4 + 3);
366
+ let offset = 0;
367
+ offset = this.writeUint32(payload, Math.abs(props.x) * fMultiplier, offset);
368
+ offset = this.writeUint32(payload, Math.abs(props.y) * fMultiplier, offset);
369
+ offset = this.writeUint32(payload, props.timeMs, offset);
370
+ payload[offset] = props.relative ? 1 : 0;
371
+ payload[offset + 1] = props.x >= 0 ? 0 : 1;
372
+ payload[offset + 2] = props.y >= 0 ? 0 : 1;
373
+ this.sendMessageToSocket(messageId, payload);
374
+ } break;
375
+ case "wc-mouse-button": {
376
+ const payload = new Uint8Array(4 + 2);
377
+ payload[0] = props.button;
378
+ payload[1] = props.pressed ? 1 : 0;
379
+ this.writeUint32(payload, props.timeMs, 2);
380
+ this.sendMessageToSocket(messageId, payload);
381
+ } break;
382
+ case "wc-mouse-sync": {
383
+ const payload = new Uint8Array(4);
384
+ this.writeUint32(payload, props.timeMs, 0);
385
+ this.sendMessageToSocket(messageId, payload);
386
+ } break;
387
+ case "wc-connect": {
388
+ this.sendMessageToSocket(messageId, new Uint8Array([props.networkType]),
389
+ textEncoder.encode(props.address));
390
+ } break;
391
+ case "wc-fs-tree": {
392
+ this.sendMessageToSocket(messageId);
393
+ } break;
394
+ case "wc-pack-fs-to-bundle": {
395
+ this.sendMessageToSocket(messageId, new Uint8Array([props.onlyChanges ? 1 : 0]));
396
+ } break;
397
+ default: {
398
+ console.log("Unhandled client message (wc):", name, messageId, props);
399
+ } break;
400
+ }
401
+ }
402
+
403
+ initMessageHandler(handler: MessageHandler) {
404
+ this.handler = (message, props) => {
405
+ props.sessionId = this.sessionId;
406
+ handler(message, props);
407
+ };
408
+ }
409
+
410
+ exit() {
411
+ this.sendMessageToSocket("wc-exit");
412
+ }
413
+
414
+ onSockdriveOpen(drive: string, read: boolean, write: boolean) {
415
+ this.handler("ws-log", {
416
+ tag: "worker",
417
+ message: "sockdrive: " + drive + ", read=" + read + ", write=" + write,
418
+ });
419
+ }
420
+
421
+ onSockdriveError(e: Error) {
422
+ this.handler("ws-err", {
423
+ tag: "panic",
424
+ message: e.message ?? "unable to open sockdrive",
425
+ });
426
+ }
427
+ }
428
+
429
+ export function createWsTransportLayer(url: string): Promise<TransportLayer> {
430
+ return new Promise<TransportLayer>((resolve) => {
431
+ let locked = false;
432
+ const inervalId = setInterval(() => {
433
+ if (locked) {
434
+ return;
435
+ }
436
+
437
+ locked = true;
438
+ const ws = new WebSocket(url);
439
+ const onSuccess = () => {
440
+ clearInterval(inervalId);
441
+ console.log("Connected to", url);
442
+ resolve(new WsTransportLayer(ws));
443
+ };
444
+
445
+ ws.addEventListener("error", (error) => {
446
+ console.error("Can't conect to ", url, error);
447
+ ws.removeEventListener("open", onSuccess);
448
+ ws.close();
449
+ locked = false;
450
+ });
451
+
452
+ ws.addEventListener("open", onSuccess);
453
+ }, 1000);
454
+ });
455
+ }