Skip to content

Commit e8d8c23

Browse files
committed
feat(web): 提供web页面使用
1 parent 480b52f commit e8d8c23

65 files changed

Lines changed: 10981 additions & 11 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/sync_versions.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const targetFiles = [
1717
{ kind: "cargo", path: "packages/unluac-wasm/Cargo.toml" },
1818
{ kind: "json", path: "packages/unluac-js/package.json" },
1919
{ kind: "package-lock", path: "packages/unluac-js/package-lock.json" },
20+
{ kind: "json", path: "packages/unluac-web/package.json" },
2021
];
2122

2223
const rootManifest = await readFile(rootManifestPath, "utf8");

.github/workflows/deploy-web.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Deploy Web
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'src/**'
8+
- 'packages/unluac-wasm/**'
9+
- 'packages/unluac-web/**'
10+
- 'Cargo.toml'
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
16+
env:
17+
NODE_VERSION: '24'
18+
WASM_PACK_VERSION: '0.14.0'
19+
WASM_BINDGEN_CLI_VERSION: '0.2.115'
20+
21+
jobs:
22+
deploy:
23+
name: Deploy to Vercel
24+
runs-on: ubuntu-24.04
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Setup Node
30+
uses: actions/setup-node@v4
31+
with:
32+
node-version: ${{ env.NODE_VERSION }}
33+
34+
- name: Setup Rust
35+
uses: dtolnay/rust-toolchain@stable
36+
with:
37+
targets: wasm32-unknown-unknown
38+
39+
- name: Restore wasm tool cache
40+
id: wasm-tool-cache
41+
uses: actions/cache@v4
42+
with:
43+
path: |
44+
~/.cargo/bin/wasm-pack
45+
~/.cargo/bin/wasm-bindgen
46+
~/.cargo/bin/wasm-bindgen-test-runner
47+
~/.cargo/bin/wasm2es6js
48+
~/.cargo/.crates2.json
49+
~/.cargo/.crates.toml
50+
key: ${{ runner.os }}-wasm-tools-wasm-pack-${{ env.WASM_PACK_VERSION }}-wasm-bindgen-${{ env.WASM_BINDGEN_CLI_VERSION }}
51+
52+
- name: Restore cargo build cache
53+
uses: actions/cache@v4
54+
with:
55+
path: |
56+
~/.cargo/registry/index
57+
~/.cargo/registry/cache
58+
~/.cargo/git/db
59+
target/
60+
key: ${{ runner.os }}-cargo-wasm-${{ hashFiles('Cargo.lock', 'src/**', 'packages/unluac-wasm/**') }}
61+
restore-keys: |
62+
${{ runner.os }}-cargo-wasm-
63+
64+
- name: Install wasm-pack
65+
if: steps.wasm-tool-cache.outputs.cache-hit != 'true'
66+
run: cargo install wasm-pack --version ${{ env.WASM_PACK_VERSION }} --locked
67+
68+
- name: Install wasm-bindgen CLI
69+
if: steps.wasm-tool-cache.outputs.cache-hit != 'true'
70+
run: cargo install wasm-bindgen-cli --version ${{ env.WASM_BINDGEN_CLI_VERSION }} --locked
71+
72+
- name: Install dependencies
73+
working-directory: packages/unluac-web
74+
run: npm install
75+
76+
- name: Build
77+
working-directory: packages/unluac-web
78+
run: npm run build
79+
80+
- name: Deploy to Vercel
81+
uses: amondnet/vercel-action@v42.2.0
82+
with:
83+
vercel-token: ${{ secrets.VERCEL_TOKEN }}
84+
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
85+
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
86+
target: production
87+
working-directory: ${{ github.workspace }}/packages/unluac-web

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
/lua/.tmp
33
/lua/build
44
/lua/sources
5-
.DS_Store
5+
.DS_Store
6+
node_modules
7+
dist
8+
.vite
9+
*.tsbuildinfo

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"backedge",
1414
"backedges",
1515
"BANDK",
16+
"bindgen",
1617
"BITRK",
1718
"BNOT",
1819
"Bxor",

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ A multi-dialect Lua decompiler written in Rust.
99

1010
Published entry points:
1111

12+
- Web interface at [unluac.x3zvawq.com](https://unluac.x3zvawq.com)
1213
- Rust crate [`unluac`](https://crates.io/crates/unluac)
1314
- Standalone CLI binaries on [GitHub Releases](https://github.com/x3zvawq/unluac-rs/releases)
1415
- npm package [`unluac-js`](https://www.npmjs.com/package/unluac-js)
@@ -40,10 +41,21 @@ The repository is organized roughly like this:
4041

4142
The project is currently distributed through these entry points:
4243

43-
1. **CLI**: use the standalone binary from GitHub Releases, or run/build `unluac-cli` from this repository.
44-
2. **Rust library**: add the published crate `unluac` to a Rust project and call the decompilation pipeline directly.
45-
3. **npm package**: install `unluac-js` for Node.js or bundler-based browser environments.
46-
4. **WebAssembly**: use `packages/unluac-wasm` directly when you want to build your own runtime wrapper on top of the wasm layer.
44+
1. **Web interface**: visit [unluac.x3zvawq.com](https://unluac.x3zvawq.com) — upload and decompile directly in the browser with no installation required.
45+
2. **CLI**: use the standalone binary from GitHub Releases, or run/build `unluac-cli` from this repository.
46+
3. **Rust library**: add the published crate `unluac` to a Rust project and call the decompilation pipeline directly.
47+
4. **npm package**: install `unluac-js` for Node.js or bundler-based browser environments.
48+
5. **WebAssembly**: use `packages/unluac-wasm` directly when you want to build your own runtime wrapper on top of the wasm layer.
49+
50+
### Web Interface
51+
52+
Visit [unluac.x3zvawq.com](https://unluac.x3zvawq.com) to upload `.luac` / `.out` files and decompile them directly in the browser — no tooling required.
53+
54+
The site is hosted on Vercel and may be inaccessible in some network environments (e.g. mainland China). If you run into connectivity issues, consider the alternatives below:
55+
56+
- Use the standalone CLI binary — works fully offline
57+
- Use the npm package to run decompilation locally in a bundled browser environment
58+
- Self-host the web interface using `packages/unluac-wasm`
4759

4860
### CLI
4961

README_cn.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
当前已经发布的对外入口包括:
1111

12+
- Web 页面 [unluac.x3zvawq.com](https://unluac.x3zvawq.com)
1213
- Rust crate [`unluac`](https://crates.io/crates/unluac)
1314
- GitHub Releases 上的独立 CLI 二进制
1415
- npm 包 [`unluac-js`](https://www.npmjs.com/package/unluac-js)
@@ -40,10 +41,21 @@
4041

4142
项目当前主要通过以下几种入口分发:
4243

43-
1. **命令行工具**:使用 GitHub Releases 提供的独立二进制,或者直接在本仓库中运行 / 构建 `unluac-cli`
44-
2. **Rust 库**:在 Rust 项目中引入已发布的 `unluac` crate,直接调用反编译 pipeline。
45-
3. **npm 包**:安装 `unluac-js`,面向 Node.js 或基于打包器的浏览器环境。
46-
4. **WebAssembly**:直接使用 `packages/unluac-wasm`,适合继续为其他语言或运行时做封装。
44+
1. **Web 页面**:直接访问 [unluac.x3zvawq.com](https://unluac.x3zvawq.com),无需安装,在浏览器中完成上传与反编译。
45+
2. **命令行工具**:使用 GitHub Releases 提供的独立二进制,或者直接在本仓库中运行 / 构建 `unluac-cli`
46+
3. **Rust 库**:在 Rust 项目中引入已发布的 `unluac` crate,直接调用反编译 pipeline。
47+
4. **npm 包**:安装 `unluac-js`,面向 Node.js 或基于打包器的浏览器环境。
48+
5. **WebAssembly**:直接使用 `packages/unluac-wasm`,适合继续为其他语言或运行时做封装。
49+
50+
### Web 页面
51+
52+
访问 [unluac.x3zvawq.com](https://unluac.x3zvawq.com) 即可直接在浏览器中上传 `.luac` / `.out` 文件进行反编译,无需安装任何工具。
53+
54+
站点部署在 Vercel 上,在部分网络环境(如中国大陆)可能存在访问限制。如遇无法访问的情况,可考虑以下替代方案:
55+
56+
- 使用 CLI 二进制(离线,无需网络)
57+
- 使用 npm 包在本地浏览器环境中运行
58+
- 基于 `packages/unluac-wasm` 自建站点
4759

4860
### 命令行工具
4961

luac.out

-219 Bytes
Binary file not shown.

packages/unluac-js/README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Requirements:
2222

2323
- `init(input?)`: initialize the wasm module explicitly
2424
- `decompile(bytes, options?)`: decompile a compiled Lua chunk and return the final source string
25+
- `decompileRich(bytes, options?)`: decompile and return structured analysis result (source + proto metadata + CFGs)
2526
- `supportedOptionValues()`: inspect supported enum-like option values
2627

2728
This package ships a slim wasm build for npm. In particular:
@@ -103,6 +104,28 @@ console.log(source);
103104
- the input must already be a compiled chunk; this package does not compile Lua source for you
104105
- the return value is always the final generated source string
105106

107+
### `decompileRich(bytes, options?)`
108+
109+
Returns a structured analysis result instead of a plain source string:
110+
111+
```ts
112+
import { decompileRich } from "unluac-js";
113+
114+
const result = await decompileRich(chunkBytes, { dialect: "lua5.1" });
115+
116+
console.log(result.source); // final Lua source
117+
console.log(result.warnings); // generation warnings
118+
console.log(result.protos); // proto metadata (DFS order)
119+
console.log(result.cfgs); // per-proto CFG with blocks and edges
120+
```
121+
122+
The result includes:
123+
124+
- `source`: generated Lua source string
125+
- `warnings`: array of generation-stage warnings
126+
- `protos`: array of `UnluacProtoMeta` with function metadata (name, line range, params, upvalues, constants, instructions, children)
127+
- `cfgs`: array of `UnluacProtoCfg` with control flow graph data (blocks with Low-IR and raw bytecode instructions, edges with type labels)
128+
106129
Supported top-level options:
107130

108131
- `dialect`
@@ -134,7 +157,7 @@ Common `decompile()` options:
134157

135158
- `dialect`: target chunk dialect such as `lua5.1`, `lua5.4`, `luajit`, or `luau`
136159
- `parse.mode`: parser mode, `strict` or `permissive`
137-
- `parse.stringEncoding`: string decoding encoding, `utf-8` or `gbk`
160+
- `parse.stringEncoding`: string decoding encoding; accepts any [Encoding Standard](https://encoding.spec.whatwg.org/) label (e.g. `utf-8`, `gbk`, `shift_jis`, `euc-kr`, `big5`)
138161
- `parse.stringDecodeMode`: string decode failure strategy, `strict` or `lossy`
139162
- `naming.mode`: naming strategy, `debug-like`, `simple`, or `heuristic`
140163
- `naming.debugLikeIncludeFunction`: whether debug-like naming should include function-shaped names

packages/unluac-js/src/index.ts

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ export type UnluacDialect =
88
| "luau";
99

1010
export type UnluacParseMode = "strict" | "permissive";
11-
export type UnluacStringEncoding = "utf-8" | "gbk";
11+
export type UnluacStringEncoding =
12+
| "utf-8"
13+
| "GBK"
14+
| "gb18030"
15+
| "Big5"
16+
| "Shift_JIS"
17+
| "EUC-JP"
18+
| "EUC-KR"
19+
| "windows-1252"
20+
| "windows-1251"
21+
| "KOI8-R"
22+
| "windows-874"
23+
| (string & {});
1224
export type UnluacStringDecodeMode = "strict" | "lossy";
1325
export type UnluacNamingMode = "debug-like" | "simple" | "heuristic";
1426
export type UnluacQuoteStyle = "prefer-double" | "prefer-single" | "min-escape";
@@ -53,6 +65,82 @@ export interface UnluacSupportedOptionValues {
5365
tableStyles: UnluacTableStyle[];
5466
}
5567

68+
// ── 结构化反编译结果(decompileRich 返回) ──
69+
70+
export type UnluacBlockKind = "normal" | "synthetic-exit";
71+
export type UnluacEdgeKind =
72+
| "fallthrough"
73+
| "jump"
74+
| "branch-true"
75+
| "branch-false"
76+
| "loop-body"
77+
| "loop-exit"
78+
| "return"
79+
| "tail-call";
80+
81+
export interface UnluacRichResult {
82+
/** 反编译生成的完整 Lua 源码 */
83+
source: string;
84+
/** 生成阶段的警告 */
85+
warnings: string[];
86+
/** proto 元数据(DFS 序展平) */
87+
protos: UnluacProtoMeta[];
88+
/** 每个 proto 的 CFG(与 protos 平行数组) */
89+
cfgs: UnluacProtoCfg[];
90+
}
91+
92+
export interface UnluacProtoMeta {
93+
/** DFS 遍历序号(0 = 主 proto) */
94+
id: number;
95+
/** 源文件名(debug info) */
96+
name: string | null;
97+
lineStart: number;
98+
lineEnd: number;
99+
numParams: number;
100+
isVararg: boolean;
101+
numUpvalues: number;
102+
numConstants: number;
103+
numInstructions: number;
104+
/** 常量池字面量列表 */
105+
constants: UnluacConstant[];
106+
/** 子 proto 的 DFS ID 列表 */
107+
children: number[];
108+
}
109+
110+
export interface UnluacConstant {
111+
/** 常量在池中的索引(0-based) */
112+
index: number;
113+
/** 类型标签 */
114+
type: "nil" | "boolean" | "integer" | "number" | "string" | "int64" | "uint64" | "complex";
115+
/** 人类可读的值表示 */
116+
display: string;
117+
}
118+
119+
export interface UnluacProtoCfg {
120+
protoId: number;
121+
blocks: UnluacCfgBlock[];
122+
edges: UnluacCfgEdge[];
123+
entryBlock: number;
124+
exitBlock: number;
125+
/** 拓扑序 block ID 列表 */
126+
blockOrder: number[];
127+
}
128+
129+
export interface UnluacCfgBlock {
130+
id: number;
131+
kind: UnluacBlockKind;
132+
/** 人类可读的 Low-IR 指令行 */
133+
instructions: string[];
134+
/** 对应的原始字节码指令行(通过 LoweringMap 映射) */
135+
rawInstructions: string[];
136+
}
137+
138+
export interface UnluacCfgEdge {
139+
from: number;
140+
to: number;
141+
kind: UnluacEdgeKind;
142+
}
143+
56144
type UnluacBytes = BufferSource | ArrayLike<number>;
57145
type UnluacInitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
58146
type WasmInitArgument =
@@ -63,6 +151,7 @@ type WasmInitArgument =
63151
interface WasmBindings {
64152
default(input?: WasmInitArgument): Promise<unknown>;
65153
decompile(bytes: Uint8Array, options: UnluacDecompileOptions): string;
154+
decompileRich(bytes: Uint8Array, options: UnluacDecompileOptions): UnluacRichResult;
66155
supportedOptionValues(): UnluacSupportedOptionValues;
67156
}
68157

@@ -138,6 +227,15 @@ export async function decompile(
138227
return bindings.decompile(toUint8Array(bytes), options);
139228
}
140229

230+
export async function decompileRich(
231+
bytes: UnluacBytes,
232+
options: UnluacDecompileOptions = {}
233+
): Promise<UnluacRichResult> {
234+
await init();
235+
const bindings = await loadBindings();
236+
return bindings.decompileRich(toUint8Array(bytes), options);
237+
}
238+
141239
export async function supportedOptionValues(): Promise<UnluacSupportedOptionValues> {
142240
await init();
143241
const bindings = await loadBindings();

packages/unluac-wasm/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use unluac::decompile::{
1717
};
1818
use unluac::parser::{ParseMode, StringDecodeMode, StringEncoding};
1919

20+
mod rich;
21+
2022
pub use unluac as core;
2123

2224
#[derive(Debug, Default, Deserialize)]
@@ -111,6 +113,21 @@ pub fn decompile_wasm(bytes: &[u8], options: JsValue) -> Result<JsValue, JsValue
111113
to_js_value(&generated_source)
112114
}
113115

116+
/// 返回结构化 JSON,包含 proto 树、CFG 和反编译源码。
117+
///
118+
/// 与 `decompile` 的区别:`decompile` 只返回源码字符串,而 `decompileRich`
119+
/// 额外返回 proto 元数据和 CFG 拓扑,供前端可视化使用。
120+
#[wasm_bindgen(js_name = decompileRich)]
121+
pub fn decompile_rich_wasm(bytes: &[u8], options: JsValue) -> Result<JsValue, JsValue> {
122+
let options = parse_wasm_options(options).map_err(WasmBridgeError::into_js_value)?;
123+
let result = run_decompile(bytes, options).map_err(|error| {
124+
WasmBridgeError::new("decompile-failed", error.to_string(), None).into_js_value()
125+
})?;
126+
127+
let rich = rich::project_rich_result(&result);
128+
to_js_value(&rich)
129+
}
130+
114131
#[wasm_bindgen(js_name = supportedOptionValues)]
115132
pub fn supported_option_values() -> Result<JsValue, JsValue> {
116133
to_js_value(&WasmSupportedOptionValues {

0 commit comments

Comments
 (0)