Skip to content

Commit 45ef140

Browse files
committed
chore: squash Tauri native refactor, docs sync, and upstream merge
- refactor: replace browser APIs with Tauri native equivalents (localStorage→LazyStore, fullscreen→getCurrentWindow, platform detection) - docs: sync EN README with simplified CN version (remove tagline, Concurrency, UI Tweaks, hexz Features) - merge: upstream/main (WebGAL Craft doc updates, keep WebGAL_k READMEs)
1 parent 9a31228 commit 45ef140

17 files changed

Lines changed: 90 additions & 171 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts
3333
# We use bun!
3434
yarn.lockhexz_k/
3535
game.hxz
36+
.svelte-kit

README.md

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
**[English](./README_EN.md)**
44

5-
> WebGAL + hexz = 加密 · 随机访问 · 差量更新的桌面端视觉小说引擎
6-
7-
基于 [WebGAL](https://github.com/OpenWebGAL/WebGAL) / [Tauri v2](https://v2.tauri.app) / [hexz](https://github.com/maincoretech/hexz_k),将游戏资源打包为独立 `.hxz` 加密归档文件,支持 Steamworks 差量更新。
5+
基于 [WebGAL](https://github.com/OpenWebGAL/WebGAL) / [Tauri v2](https://v2.tauri.app) / [hexz](https://github.com/maincoretech/hexz_k),将游戏资源打包为独立 `.hxz` 归档文件,支持 Steamworks 差量更新。
86

97
---
108

@@ -78,16 +76,6 @@ flowchart LR
7876
| 更新需重新部署全部文件 | `.hxz` 独立于可执行文件,支持 Steamworks 差量更新 |
7977
| 客户端每次请求完整资源 | O(1) 随机访问,按需读取单个文件 |
8078

81-
### 并发性能
82-
83-
| 上游 WebGAL | WebGAL_k |
84-
|-------------|----------|
85-
| 浏览器原生并发 | `Arc<ResourcePack>` 无锁并发,protocol + IPC 多通道并行 |
86-
87-
### UI 微调
88-
89-
- 文本框移除 `backdrop-filter: blur()`,背景加深
90-
9179
---
9280

9381
## 构建
@@ -106,19 +94,6 @@ cp game.hxz src-tauri/target/release/bundle/macos/webgal-k.app/Contents/MacOS/
10694

10795
`find_hexz()` 自动搜索 exe 同目录、上级目录、macOS `.app` 同级。
10896

109-
---
110-
111-
## hexz 特性利用
112-
113-
| 特性 | 实现 |
114-
|------|------|
115-
| **加密** | AES-256-GCM,`HEXZ_PASSWORD` 环境变量 |
116-
| **随机访问** | O(1) 索引查找,按需读取单文件 |
117-
| **并发读** | `Arc<ResourcePack>`,protocol + IPC 并行 |
118-
| **差量更新** | `.hxz` 独立于可执行文件,Steamworks 友好 |
119-
120-
---
121-
12297
## 许可
12398

12499
MIT · 基于 [WebGAL](https://github.com/OpenWebGAL/WebGAL) 构建

README_EN.md

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
**[中文版](./README.md)**
44

5-
> WebGAL + hexz = Encrypted · Random-access · Delta-updatable desktop visual novel engine
6-
7-
Built on [WebGAL](https://github.com/OpenWebGAL/WebGAL) / [Tauri v2](https://v2.tauri.app) / [hexz](https://github.com/maincoretech/hexz_k). Packs game assets into a single encrypted `.hxz` archive, fully compatible with Steamworks delta patching.
5+
Built on [WebGAL](https://github.com/OpenWebGAL/WebGAL) / [Tauri v2](https://v2.tauri.app) / [hexz](https://github.com/maincoretech/hexz_k). Packs game assets into a single `.hxz` archive, fully compatible with Steamworks delta patching.
86

97
---
108

@@ -78,16 +76,6 @@ flowchart LR
7876
| Full redeploy on update | `.hxz` independent of executable, Steamworks delta patch ready |
7977
| Client fetches full resource per request | O(1) random access, single-file reads on demand |
8078

81-
### Concurrency
82-
83-
| Upstream WebGAL | WebGAL_k |
84-
|-----------------|----------|
85-
| Browser-native concurrency | `Arc<ResourcePack>` lock-free, protocol + IPC parallel channels |
86-
87-
### UI Tweaks
88-
89-
- Textbox: removed `backdrop-filter: blur()`, darker background
90-
9179
---
9280

9381
## Build
@@ -106,19 +94,6 @@ cp game.hxz src-tauri/target/release/bundle/macos/webgal-k.app/Contents/MacOS/
10694

10795
`find_hexz()` searches exe directory, parent directory, and macOS `.app` bundle root.
10896

109-
---
110-
111-
## hexz Features in Use
112-
113-
| Feature | Implementation |
114-
|---------|---------------|
115-
| **Encryption** | AES-256-GCM, `HEXZ_PASSWORD` env var |
116-
| **Random access** | O(1) index lookup, per-file reads |
117-
| **Concurrent reads** | `Arc<ResourcePack>`, protocol + IPC in parallel |
118-
| **Delta updates** | `.hxz` independent of executable, Steamworks-ready |
119-
120-
---
121-
12297
## License
12398

12499
MIT · Built on [WebGAL](https://github.com/OpenWebGAL/WebGAL)

packages/parser/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@
1818
"license": "MPL-2.0",
1919
"dependencies": {
2020
"chevrotain": "^10.5.0",
21-
"cloudlogjs": "^1.0.11",
22-
"lodash": "^4.17.23",
2321
"tsx": "^4"
2422
},
2523
"devDependencies": {
26-
"@types/lodash": "^4.14.189",
2724
"@types/node": "^18.19.130",
2825
"@vitest/coverage-v8": "^2",
2926
"tslib": "^2.4.1",

packages/parser/src/interface/sceneInterface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* 语句类型
33
*/
4-
import { sceneEntry, ISceneEntry } from './runtimeInterface';
4+
import { ISceneEntry } from './runtimeInterface';
55
import { fileType } from './assets';
66

77
export enum commandType {

packages/parser/src/sceneParser.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ISentence,
66
} from './interface/sceneInterface';
77
import { scriptParser } from './scriptParser/scriptParser';
8-
import uniqWith from 'lodash/uniqWith';
98
import { fileType } from './interface/assets';
109
import { ConfigMap } from './config/scriptConfig';
1110

@@ -31,13 +30,9 @@ export const sceneParser = (
3130
): IScene => {
3231
const rawSentenceList = rawScene.replaceAll('\r', '').split('\n'); // 原始句子列表
3332

34-
// 去分号留到后面去做了,现在注释要单独处理
35-
const rawSentenceListWithoutEmpty = rawSentenceList;
36-
// .map((sentence) => sentence.split(";")[0])
37-
// .filter((sentence) => sentence.trim() !== "");
38-
let assetsList: Array<IAsset> = []; // 场景资源列表
39-
let subSceneList: Array<string> = []; // 子场景列表
40-
const sentenceList: Array<ISentence> = rawSentenceListWithoutEmpty.map(
33+
const assetsList: Array<IAsset> = []; // 场景资源列表
34+
const subSceneList: Array<string> = []; // 子场景列表
35+
const sentenceList: Array<ISentence> = rawSentenceList.map(
4136
(sentence, index) => {
4237
const returnSentence: ISentence = scriptParser(
4338
sentence,
@@ -47,21 +42,26 @@ export const sceneParser = (
4742
index,
4843
);
4944
// 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
50-
assetsList = [...assetsList, ...returnSentence.sentenceAssets];
51-
subSceneList = [...subSceneList, ...returnSentence.subScene];
45+
for (const a of returnSentence.sentenceAssets) assetsList.push(a);
46+
for (const s of returnSentence.subScene) subSceneList.push(s);
5247
return returnSentence;
5348
},
5449
);
5550

56-
// 开始资源的预加载
57-
assetsList = uniqWith(assetsList); // 去重
58-
assetsPrefetcher(assetsList);
51+
// 开始资源的预加载(按 url 去重)
52+
const seen = new Set<string>();
53+
const uniqueAssets = assetsList.filter((a) => {
54+
if (seen.has(a.url)) return false;
55+
seen.add(a.url);
56+
return true;
57+
});
58+
assetsPrefetcher(uniqueAssets);
5959

6060
return {
6161
sceneName: sceneName, // 场景名称
6262
sceneUrl: sceneUrl,
6363
sentenceList: sentenceList, // 语句列表
64-
assetsList: assetsList, // 资源列表
64+
assetsList: uniqueAssets, // 资源列表
6565
subSceneList: subSceneList, // 子场景列表
6666
};
6767
};

packages/parser/src/sceneTextPreProcessor.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,41 +98,40 @@ function isEmptyLine(line: string): boolean {
9898
*/
9999
function sceneTextPreProcessPassTwo(lines: string[]): string[] {
100100
const processedLines: string[] = [];
101-
let currentMultilineContent = "";
101+
const parts: string[] = [];
102102
let placeHolderLines: string[] = [];
103103

104104
function concat(line: string) {
105105
let trimmed = line.trim();
106106
if (trimmed.startsWith('-')) {
107-
trimmed = " " + trimmed;
107+
trimmed = ' ' + trimmed;
108108
}
109-
currentMultilineContent = currentMultilineContent + trimmed;
109+
parts.push(trimmed);
110110
placeHolderLines.push(placeholderLine(line));
111111
}
112112

113113
for (const line of lines) {
114-
console.log(line);
115114
if (line.endsWith('\\')) {
116115
const trueLine = line.slice(0, -1);
117116

118-
if (currentMultilineContent === "") {
117+
if (parts.length === 0) {
119118
// first line
120-
currentMultilineContent = trueLine;
119+
parts.push(trueLine);
121120
} else {
122121
// middle line
123122
concat(trueLine);
124123
}
125124
continue;
126125
}
127126

128-
if (currentMultilineContent !== "") {
127+
if (parts.length > 0) {
129128
// end line
130129
concat(line);
131-
processedLines.push(currentMultilineContent);
130+
processedLines.push(parts.join(''));
132131
processedLines.push(...placeHolderLines);
133132

134133
placeHolderLines = [];
135-
currentMultilineContent = "";
134+
parts.length = 0;
136135
continue;
137136
}
138137

packages/parser/src/scriptParser/argsParser.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,8 @@ export function argsParser(
1212
assetSetter: (fileName: string, assetType: fileType) => string,
1313
): Array<arg> {
1414
const returnArrayList: Array<arg> = [];
15-
// 处理参数
16-
// 不要去空格
17-
let newArgsRaw = argsRaw.replace(/ /g, ' ');
18-
// 分割参数列表
19-
let rawArgsList: Array<string> = newArgsRaw.split(' -');
20-
// 去除空字符串
21-
rawArgsList = rawArgsList.filter((e) => {
22-
return e !== '';
23-
});
15+
// 分割参数列表(以 " -" 作为分隔)
16+
const rawArgsList: Array<string> = argsRaw.split(' -').filter((e) => e !== '');
2417
rawArgsList.forEach((e) => {
2518
const equalSignIndex = e.indexOf('=');
2619
let argName = e.slice(0, equalSignIndex).trim();
Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
import { commandType } from '../interface/sceneInterface';
22
import { fileType } from '../interface/assets';
33

4+
/** Maps command types to their asset file type for content path resolution. */
5+
const CONTENT_FILE_TYPE: Partial<Record<commandType, fileType>> = {
6+
[commandType.playEffect]: fileType.vocal,
7+
[commandType.changeBg]: fileType.background,
8+
[commandType.changeFigure]: fileType.figure,
9+
[commandType.bgm]: fileType.bgm,
10+
[commandType.callScene]: fileType.scene,
11+
[commandType.changeScene]: fileType.scene,
12+
[commandType.miniAvatar]: fileType.figure,
13+
[commandType.video]: fileType.video,
14+
[commandType.unlockBgm]: fileType.bgm,
15+
[commandType.unlockCg]: fileType.background,
16+
};
17+
418
/**
519
* 解析语句内容的函数,主要作用是把文件名改为绝对地址或相对地址(根据使用情况而定)
620
* @param contentRaw 原始语句内容
@@ -11,60 +25,27 @@ import { fileType } from '../interface/assets';
1125
export const contentParser = (
1226
contentRaw: string,
1327
type: commandType,
14-
assetSetter: any,
28+
assetSetter: (name: string, ft: fileType) => string,
1529
) => {
1630
if (contentRaw === 'none' || contentRaw === '') {
1731
return '';
1832
}
19-
switch (type) {
20-
case commandType.playEffect:
21-
return assetSetter(contentRaw, fileType.vocal);
22-
case commandType.changeBg:
23-
return assetSetter(contentRaw, fileType.background);
24-
case commandType.changeFigure:
25-
return assetSetter(contentRaw, fileType.figure);
26-
case commandType.bgm:
27-
return assetSetter(contentRaw, fileType.bgm);
28-
case commandType.callScene:
29-
return assetSetter(contentRaw, fileType.scene);
30-
case commandType.changeScene:
31-
return assetSetter(contentRaw, fileType.scene);
32-
case commandType.miniAvatar:
33-
return assetSetter(contentRaw, fileType.figure);
34-
case commandType.video:
35-
return assetSetter(contentRaw, fileType.video);
36-
case commandType.choose:
37-
return getChooseContent(contentRaw, assetSetter);
38-
case commandType.unlockBgm:
39-
return assetSetter(contentRaw, fileType.bgm);
40-
case commandType.unlockCg:
41-
return assetSetter(contentRaw, fileType.background);
42-
default:
43-
return contentRaw;
33+
if (type === commandType.choose) {
34+
return getChooseContent(contentRaw, assetSetter);
4435
}
36+
const ft = CONTENT_FILE_TYPE[type];
37+
return ft ? assetSetter(contentRaw, ft) : contentRaw;
4538
};
4639

47-
function getChooseContent(contentRaw: string, assetSetter: any): string {
40+
function getChooseContent(contentRaw: string, assetSetter: (name: string, ft: fileType) => string): string {
4841
const chooseList = contentRaw.split(/(?<!\\)\|/);
49-
const chooseKeyList: Array<string> = [];
50-
const chooseValueList: Array<string> = [];
42+
const parts: string[] = [];
5143
for (const e of chooseList) {
52-
chooseKeyList.push(e.split(/(?<!\\):/)[0] ?? '');
53-
chooseValueList.push(e.split(/(?<!\\):/)[1] ?? '');
54-
}
55-
const parsedChooseList = chooseValueList.map((e) => {
56-
if (e.match(/\./)) {
57-
return assetSetter(e, fileType.scene);
58-
} else {
59-
return e;
60-
}
61-
});
62-
let ret = '';
63-
for (let i = 0; i < chooseKeyList.length; i++) {
64-
if (i !== 0) {
65-
ret = ret + '|';
66-
}
67-
ret = ret + `${chooseKeyList[i]}:${parsedChooseList[i]}`;
44+
const colonIdx = e.search(/(?<!\\):/);
45+
const key = colonIdx >= 0 ? e.slice(0, colonIdx) : e;
46+
const val = colonIdx >= 0 ? e.slice(colonIdx + 1) : '';
47+
const resolved = val.includes('.') ? assetSetter(val, fileType.scene) : val;
48+
parts.push(`${key}:${resolved}`);
6849
}
69-
return ret;
50+
return parts.join('|');
7051
}

packages/webgal/src/Core/util/gameAssetsAccess/assetSetter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export enum fileType {
2121
* @param assetType 资源类型
2222
* @return {string} 处理后的资源路径(绝对或相对)
2323
*/
24-
// macOS: hexz:// protocol; Windows: same-origin ./game/ via Service Worker
25-
const isWindows = /windows/i.test((navigator as any).userAgentData?.platform ?? navigator.platform ?? '');
24+
// On Tauri desktop: WKWebView → MacIntel, WebView2 → Win32
25+
const isWindows = /win/i.test(navigator.platform ?? '');
2626
const PREFIX = isWindows ? './game/' : 'hexz://localhost/';
2727

2828
/** Resolve a hexz-archive path to the platform-appropriate URL. */

0 commit comments

Comments
 (0)