HelloSun commited on
Commit
3085e1b
·
verified ·
1 Parent(s): 7cbbe8b

Build and deploy 小貓中文 (FreeDOS, self-hosted js-dos)

Browse files
Files changed (5) hide show
  1. README.md +20 -8
  2. dev/HANDOFF.md +29 -19
  3. dev/xiaomao-dev.zip +2 -2
  4. dos.b64.js +0 -0
  5. index.html +4 -0
README.md CHANGED
@@ -10,20 +10,32 @@ sdk: static
10
 
11
  DOS 常駐(TSR)的 Big5 中文顯示與注音輸入外掛,在瀏覽器(含手機)中以 FreeDOS + js-dos 6.22 執行。載入後回到一般 DOS 提示字元,DOS 指令照常使用,並可直接輸入、顯示中文。全部元件皆為開源,建置時由上游下載並驗證雜湊。
12
 
13
- ## 運作方式
14
 
15
- - `XIAOMAO.EXE` 常駐約 10 KB 傳統記憶體;字型與詞庫放在 XMS(FreeDOS 開機片以 HimemX 提供)。
16
- - 載入時(及再次執行 `XIAOMAO`)才顯示常駐大小、可用傳統記憶體與 XMS(即時 MEM),沒有狀態列。
17
- - 顯示:攔截 INT 29h,遇到 Big5 雙位元組時,把 16×16 字形拆成兩個 8×16 字塊寫入 VGA 字型,再用原本的 INT 29h 輸出,游標、換行與捲動由 DOS 處理。80×25 文字模式,切為 8 點寬字元。
18
- - 輸入:攔截 INT 16h。`Ctrl+Space` 切換注音/英數;注音選字列顯示於畫面最底列,用完即還原。`Backspace` 一次刪一個中文字。
19
- - INT 2Fh `AX=D7A0h` 防止重複載入。
 
 
 
 
 
 
 
20
 
21
  ## 操作
22
 
23
  - 點選 DOS 畫面後使用實體鍵盤,或用畫面下方的虛擬鍵盤(含 Shift、`\`、`:`、Tab)。
24
- - `Ctrl+Space` 切換注音/英數(網頁有按鈕)。
25
  - 注音採倚天鍵位;輸入音節後按聲調鍵(`6 3 4 7`)或空白鍵出候選字,按 `1`–`9` 或空白鍵選字。
26
- - 試用:`TYPE DEMO.TXT`。限制:直接寫入顯示記憶體的程式不會顯示中文。
 
 
 
 
 
27
 
28
  ## 一鍵建置與部署
29
 
 
10
 
11
  DOS 常駐(TSR)的 Big5 中文顯示與注音輸入外掛,在瀏覽器(含手機)中以 FreeDOS + js-dos 6.22 執行。載入後回到一般 DOS 提示字元,DOS 指令照常使用,並可直接輸入、顯示中文。全部元件皆為開源,建置時由上游下載並驗證雜湊。
12
 
13
+ ## 運作方式(傳統中文系統作法)
14
 
15
+ - 程式看到的仍是 80×25 文字模式:文字畫面保存在 `B800:0000`(影子文字緩衝區),所以 **PE2 這類直接寫顯示記憶體的程式**與使用 INT 10h 的程式都能照常運作。
16
+ - 實際顯示是 640×400 的 16 色繪圖模式;常駐程式把影子文字畫成像素:英數用 ROM 8×16 字型,Big5 雙位元組用放在 XMS 的 16×16 字型。**畫面上不同漢字數量沒有上限**(整頁約 900 個不同漢字已測過)。
17
+ - 攔截 INT 10h:完整模擬文字模式 BIOS(模式設定、游標、捲動、讀寫字元、TTY、字串);程式要求繪圖模式時(例如 13h)直接交給 BIOS,回到文字模式時自動恢復。
18
+ - 攔截 INT 16h:`Ctrl+Space` 切換注音/英數,選好的字以兩個按鍵位元組交給程式,同時是畫面重繪的觸發點;注音候選列畫在最底列,用完即還原。
19
+ - `Ctrl+\` 切換 Big5 顯示(遇到使用 CP437 畫框字元的程式時關閉)。INT 2Fh `AX=D7A0h` 防重複載入。
20
+ - 常駐約 15 KB 傳統記憶體;字型與詞庫在 XMS(FreeDOS 開機片以 HimemX 提供)。載入時(及再次執行 `XIAOMAO`)顯示常駐大小、可用傳統記憶體與 XMS(即時 MEM)。
21
+
22
+ ## 附加軟體(開機片內)
23
+
24
+ - `XEDIT.EXE`:仿 PE2 的全螢幕文字編輯器,直接寫 `B800` 並用 INT 10h/16h,可編輯中文(`XEDIT DEMO.TXT`;F2 存檔、F3/Esc 離開)。
25
+ - FreeDOS:`COMMAND.COM`(FreeCOM)、`MEM`、`MORE`、`XCOPY`、`ATTRIB`、`DELTREE`;`HIMEMX.EXE`(XMS)。
26
+ - `DEMO.TXT`:中文示範文字(`TYPE DEMO.TXT`)。
27
 
28
  ## 操作
29
 
30
  - 點選 DOS 畫面後使用實體鍵盤,或用畫面下方的虛擬鍵盤(含 Shift、`\`、`:`、Tab)。
31
+ - `Ctrl+Space` 切換注音/英數(網頁有按鈕);`Ctrl+\` 切換 Big5 顯示(網頁有按鈕)。
32
  - 注音採倚天鍵位;輸入音節後按聲調鍵(`6 3 4 7`)或空白鍵出候選字,按 `1`–`9` 或空白鍵選字。
33
+
34
+ ## 限制
35
+
36
+ - 不支援直接改寫 CRTC/VGA 暫存器做文字模式特效(游標位置暫存器除外)、載入自訂字型、閃爍屬性(以高亮背景代替)。
37
+ - 文字畫面以約 18 次/秒更新(隨計時器),極快速的畫面動畫會有延遲。
38
+ - 只有注音輸入法,候選字最多 9 個。
39
 
40
  ## 一鍵建置與部署
41
 
dev/HANDOFF.md CHANGED
@@ -1,7 +1,7 @@
1
  # 小貓中文 — 接手說明 (HANDOFF)
2
 
3
  目標:DOS 常駐 (TSR) Big5 中文顯示 + 注音輸入,跑在 js-dos 6.22(瀏覽器/手機),部署為 HF static Space。
4
- 傳統記憶體越少越好(現況常駐約 10 KB,不強制 600K)。授權 GPL-3.0-only。全部元件開源,不含專有軟體。
5
 
6
  ## 一鍵流程
7
  `HF_TOKEN=... HF_SPACE=user/space ./build.sh`(兩者是腳本開頭變數,token 不入檔)。空目錄也能跑:自動建 Python venv、下載並以雜湊驗證
@@ -12,31 +12,41 @@ Open Watcom 1.9、JWasm+HimemX 原始碼(現場編成 HIMEMX.EXE)、GNU Unif
12
  - FreeDOS 開機 → `FDCONFIG.SYS` 載 HIMEMX → `FDAUTO.BAT` 載 XIAOMAO.EXE → 回到 `A:\>`;顯示 MEM;`Ctrl+Space` 注音;候選列在最底列;Backspace 刪整個中文字。
13
  - 手機尺寸(390x844)+觸控虛擬鍵盤驗證過;DOSBox 核心與開機片都自架,不依賴外部 CDN。
14
 
15
- ## 架構
16
- - `src/xiaomao.c`:TSR。`key_service`(INT16)、`out_filter`(INT29)、載入器 `main`。
17
- - `src/xms.asm` (NASM OMF):XMS 包裝、INT16/29/2F 掛勾(私有 1 KB 堆疊在 _TEXT,SS=CS)、`vga_tile`(INT10 AX=1100h 載入 8x16 字塊)、`vga_8dot`。
18
- helper 只能改 AX;Watcom 暫存器呼叫 AX,DX,BX,CX;large model 用 `retf`;符號後綴 `_`。
19
- - 顯示:Big5 前導+後隨 → 取 32 byte 字形(XMS) → 拆左右 8x16 → 載入 VGA 字塊 → 以舊 INT29 輸出兩個字塊字元。
20
- 字塊池:0x80-0xE5(51 對)+低控制碼 12 對=63 對(掃描螢幕找未使用者再覆寫);0xE6-0xFF(13 對)專給底列候選列。
21
- - 輸入:INT16 AH=0/1/0x10/0x11 掛勾 → IME 狀態機 → Big5 兩位元組入 16 字佇列;`run` 計數讓 Backspace 一次退兩格。
 
 
 
 
 
 
 
22
  - 資料:`FONT.DAT`("ETNF" 18 byte 頭, ASCII 256x16, CJK 32 byte/字, 索引 (lead-0xA1)*157+trail索引) 與 `IME.DAT`("ETNI", 12 byte 頭, 5 byte 記錄 key/offset/count),載入 XMS。
23
- key=(((ini*4+med)*14+fin)*5+tone);XMS 搬移長度必須為偶數。
24
- - 常駐大小:build.sh 兩階段編譯,由連結 map 的 STACK 段位址 +0x10 得 `TSR_PARAS`;init 用大緩衝放堆疊(不常駐)。
25
  - 防重複載入 INT2F AX=D7A0h→584Dh;再次執行 XIAOMAO 只顯示 MEM。
 
26
  - 網頁:`index.html`。HF 對二進位檔回 302 無 CORS,所以 `emu.b64.js`(wdosbox.js+wasm 的 base64) 與 `dos.b64.js`(開機片 zip 的 base64) 以 Blob URL 供 js-dos;
27
- 用 monkeypatch `XMLHttpRequest.prototype.open` 把 `wdosbox.js`/`wdosbox.wasm.js` 對應到 Blob URL(js-dos 以 `.js`→`.wasm.js` 推導 wasm 檔名)。
28
- - FreeDOS 開機片:取 FD14 `144m/x86BOOT.img`,刪除安裝用檔,換 `dos/FDCONFIG.SYS`、`dos/FDAUTO.BAT`(CRLF 由 build.sh 轉)。FreeDOS 主控台輸出走 INT 29h(已驗證)。
29
 
30
  ## 編譯注意
31
  Open Watcom 1.9:`wcl -bt=dos -ml -0 -os -s -zu -k6144`(`-s` 必要:私有堆疊上不可堆疊檢查)。無 `_AX` 偽暫存器;`_psp` 需自行 `extern unsigned _psp;`。
32
 
33
  ## 測試方式
34
- 無本機 DOS 模擬器:用 Playwright chromium + `python3 -m http.server` 載入 `dist/`,`page.keyboard.press('Control+Space')` 等,截圖檢查。
35
- Chromium 旗標:`--no-sandbox --disable-gpu --disable-dev-shm-usage`。
 
36
 
37
  ## 已知限制 / 待辦
38
- 1. 直接寫 B800 的程式(EDIT 等)不顯示中文;CP437 畫框字元連續出現時會被誤判為 Big5。可加開關。
39
- 2. 同畫面不同漢字超過 63 個會互相覆蓋(可考慮 512 字元字型模式)。
40
- 3. 輸入法只有注音;候選限 9 個無翻頁;無詞組/聯想。
41
- 4. 可加 FreeDOS `LH`/UMB(需 JEMM386)進一步降低傳統記憶體;目前 TSR 約 10 KB 已很小。
42
- 5. 尚未做:自動化回歸測試(Playwright 腳本可放 `tests/`)。
 
 
1
  # 小貓中文 — 接手說明 (HANDOFF)
2
 
3
  目標:DOS 常駐 (TSR) Big5 中文顯示 + 注音輸入,跑在 js-dos 6.22(瀏覽器/手機),部署為 HF static Space。
4
+ 傳統記憶體越少越好(現況常駐約 15.6 KB,不強制 600K)。授權 GPL-3.0-only。全部元件開源,不含專有軟體。
5
 
6
  ## 一鍵流程
7
  `HF_TOKEN=... HF_SPACE=user/space ./build.sh`(兩者是腳本開頭變數,token 不入檔)。空目錄也能跑:自動建 Python venv、下載並以雜湊驗證
 
12
  - FreeDOS 開機 → `FDCONFIG.SYS` 載 HIMEMX → `FDAUTO.BAT` 載 XIAOMAO.EXE → 回到 `A:\>`;顯示 MEM;`Ctrl+Space` 注音;候選列在最底列;Backspace 刪整個中文字。
13
  - 手機尺寸(390x844)+觸控虛擬鍵盤驗證過;DOSBox 核心與開機片都自架,不依賴外部 CDN。
14
 
15
+ ## 架構(傳統「中文系統」作法:文字影子 + 繪圖顯示)
16
+ - `src/xiaomao.c`:TSR。`video_service`(INT10 文字模式 BIOS 模擬)、`refresh`(渲染器)、`key_service`(INT16 + 注音)、載入器 `main`。
17
+ - `src/xms.asm` (NASM OMF):XMS 包裝、INT10/16/2F 掛勾(私有 2 KB 堆疊在 _TEXT,SS=CS;INT10 掛勾把暫存器框架以遠指標交給 C)、`bios10_call`、`get_rom_font`(INT10 AX=1130h)、`cpu_idle`。
18
+ helper 只能改 AX;Watcom 暫存器呼叫 AX,DX,BX,CX(第二參數在 DX);large model 用 `retf`;符號後綴 `_`。
19
+ - 顯示:BIOS 模式 12h(640x480x16),CRTC 垂直顯示結束改 399(只顯示 400 線=25 列x16)。程式眼中仍是模式 3:BDA(0040:xx)被改寫成模式 3/80 欄/25 列。
20
+ 影子文字在 `B800:0000`;畫面像素在平面偏移 0000-7CFF,經 A000 視窗寫入。渲染器每 tick(INT16 輪詢、阻塞前強制)逐列比對影子與「已繪副本」(`B800:6000` 字元/屬性、`B800:7000` 種類),只重畫變動的格子。
21
+ Big5 偵測:前導 A1-F9 + 後隨 40-7E/A1-FE(`Ctrl+\` 可關);漢字 16x16 由 XMS 取(64 項快取),ASCII 用 ROM 字型。游標以底線疊畫,BDA/CRTC 游標暫存器皆會同步。
22
+ - **DOSBox 的 VGA 陷阱(都已實測,別再踩)**:
23
+ 1. 模式 12h 下 `B800` 不是 RAM;GC 雜項暫存器(GC6) map=3(B8000) 會讓 DOSBox 變成 CGA 模式。
24
+ 2. 解法:GC6=0x01(A0000 起 128KB 視窗)。此時 `B800:0000` 對應平面偏移 0x8000(寫入 map mask=0Fh、讀 plane 0,行為如 RAM),與 A000 低 32 KB 的畫面互不重疊,且完全不需切換視窗。
25
+ 3. 寫模式 2 的背景填色在 DOSBox 無效;改用寫模式 0 + Set/Reset(GC0=色、GC1=0F、GC8=位元遮罩)。
26
+ 4. 部分遮罩寫入前必須「真的讀一次」同位址以載入 latch(用 volatile sink 變數,否則編譯器會丟掉讀取,未遮罩位元會帶入垃圾)。
27
+ 5. 畫完一列必須還原 GC1=0、GC8=FF、GC0=0,否則程式之後寫 B800 會被 Set/Reset 污染。
28
+ - 輸入:INT16 AH=0/1/0x10/0x11 掛勾 → IME 狀態機 → Big5 兩位元組入 16 字佇列。BS 加倍(一次退兩格)只在 DOS 內部讀鍵時(InDOS≠0)啟用,直接呼叫 INT16 的程式(PE2/XEDIT)收到單一 BS。
29
  - 資料:`FONT.DAT`("ETNF" 18 byte 頭, ASCII 256x16, CJK 32 byte/字, 索引 (lead-0xA1)*157+trail索引) 與 `IME.DAT`("ETNI", 12 byte 頭, 5 byte 記錄 key/offset/count),載入 XMS。
30
+ key=(((ini*4+med)*14+fin)*5+tone);XMS 搬移長度必須為偶數;XMS 只在安全點(INT16/INT10 內)呼叫,不在計時器 ISR。
31
+ - 常駐大小:build.sh 兩階段編譯,由連結 map 的 STACK 段位址 +0x10 得 `TSR_PARAS`;init 用大緩衝(含 4000 B 舊畫面)放堆疊(`-k10240`,不常駐)。
32
  - 防重複載入 INT2F AX=D7A0h→584Dh;再次執行 XIAOMAO 只顯示 MEM。
33
+ - 附加軟體:`src/addons/xedit.c`(PE2 風格直接寫 B800 的編輯器,用來驗證直接寫顯��記憶體相容性)。
34
  - 網頁:`index.html`。HF 對二進位檔回 302 無 CORS,所以 `emu.b64.js`(wdosbox.js+wasm 的 base64) 與 `dos.b64.js`(開機片 zip 的 base64) 以 Blob URL 供 js-dos;
35
+ monkeypatch `XMLHttpRequest.prototype.open` 把 `wdosbox.js`/`wdosbox.wasm.js` 對應到 Blob URL(js-dos 以 `.js`→`.wasm.js` 推導 wasm 檔名)。
36
+ - FreeDOS 開機片:取 FD14 `144m/x86BOOT.img`,刪除安裝用檔,換 `dos/FDCONFIG.SYS`、`dos/FDAUTO.BAT`(CRLF 由 build.sh 轉)。FreeDOS 主控台輸出經 INT 10h TTY(已驗證)。
37
 
38
  ## 編譯注意
39
  Open Watcom 1.9:`wcl -bt=dos -ml -0 -os -s -zu -k6144`(`-s` 必要:私有堆疊上不可堆疊檢查)。無 `_AX` 偽暫存器;`_psp` 需自行 `extern unsigned _psp;`。
40
 
41
  ## 測試方式
42
+ 無本機 DOS 模擬器:`python3 tests/smoke.py --dist dist --chromium <chrome>`(Playwright,截圖到 /tmp/xm-shots,只做粗略像素斷言;請人工看截圖)。
43
+ 自己寫測試 EXE 時:用 build 裡的 Open Watcom 編譯,放進映像(參考 build.sh 的組映像段),改 `dos/FDAUTO.BAT` 即可。
44
+ Chromium 旗標:`--no-sandbox --disable-gpu --disable-dev-shm-usage`。截圖檔寫入有延遲,讀檔前稍等。
45
 
46
  ## 已知限制 / 待辦
47
+ 1. 不支援程式直接改 CRTC/VGA 暫存器做文字特效(游標暫存器除外)、載入文字模式字型、閃爍屬性。
48
+ 2. 渲染為 tick 驅動(約 18 fps);可考慮在 INT10 寫入後立即小範圍重畫,或重新程式化 PIT 提高更新率。
49
+ 3. 輸入法只有注音;候選限 9 個無翻頁;無詞組/聯想;可加倚天/倉頡/大易。
50
+ 4. Big5 與 CP437 畫框字元衝突只能用 `Ctrl+\` 手動切換;可加自動偵測。
51
+ 5. 可加 JEMM386/UMB 讓 TSR 載入高記憶體(現約 15 KB 傳統記憶體)。
52
+ 6. 自動化測試只有 smoke test;可加截圖比對基準。
dev/xiaomao-dev.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:deb6dab953d76c514eac29d2aae5414820799196d0fd20e74d231d1cdf19f521
3
- size 41092
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d159ee237190b9a30ace795167758842b46d095b5a78e4bf881cb665bf44c148
3
+ size 49837
dos.b64.js CHANGED
The diff for this file is too large to render. See raw diff
 
index.html CHANGED
@@ -45,6 +45,7 @@
45
  <div id="screen-wrap"><canvas id="dosbox" width="640" height="480" aria-label="DOS emulator screen"></canvas></div>
46
  <div class="toolbar">
47
  <button id="ime-toggle" type="button">Ctrl+Space 注音切換</button>
 
48
  <button id="fullscreen" type="button">全螢幕</button>
49
  <span class="spacer"></span>
50
  <span id="message" role="status">正在載入 FreeDOS…</span>
@@ -92,6 +93,9 @@
92
  };
93
  window.etImeToggle = toggleIme;
94
  document.getElementById('ime-toggle').addEventListener('click', toggleIme);
 
 
 
95
  document.getElementById('fullscreen').addEventListener('click', () => controller && controller.fullscreen());
96
  document.getElementById('alt-x').addEventListener('click', () => {
97
  send(18, true); tap(88); window.setTimeout(() => send(18, false), 130);
 
45
  <div id="screen-wrap"><canvas id="dosbox" width="640" height="480" aria-label="DOS emulator screen"></canvas></div>
46
  <div class="toolbar">
47
  <button id="ime-toggle" type="button">Ctrl+Space 注音切換</button>
48
+ <button id="big5-toggle" type="button">Big5 顯示開關</button>
49
  <button id="fullscreen" type="button">全螢幕</button>
50
  <span class="spacer"></span>
51
  <span id="message" role="status">正在載入 FreeDOS…</span>
 
93
  };
94
  window.etImeToggle = toggleIme;
95
  document.getElementById('ime-toggle').addEventListener('click', toggleIme);
96
+ document.getElementById('big5-toggle').addEventListener('click', () => {
97
+ send(17, true); tap(220); window.setTimeout(() => send(17, false), 130);
98
+ });
99
  document.getElementById('fullscreen').addEventListener('click', () => controller && controller.fullscreen());
100
  document.getElementById('alt-x').addEventListener('click', () => {
101
  send(18, true); tap(88); window.setTimeout(() => send(18, false), 130);