esp_extractor 0.4.0

A Rust library for extracting and applying translations to Bethesda ESP/ESM/ESL files
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# ESP字符串提取工具 (esp_extractor)


[![Crates.io](https://img.shields.io/crates/v/esp_extractor.svg)](https://crates.io/crates/esp_extractor)
[![Documentation](https://docs.rs/esp_extractor/badge.svg)](https://docs.rs/esp_extractor)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)](https://opensource.org/licenses/MIT)

一个用于处理Bethesda游戏引擎(ESP/ESM/ESL)文件和字符串文件的 **现代化 Rust 库**。

**v0.4.0 重大更新** 🎉 引入了全新的架构设计,遵循 SOLID 原则,提供更灵活、更易用的 API。

## ✨ 核心特性


- 🏗️ **分层架构** - IO 抽象层 + 编辑器层,职责清晰
- 🎯 **智能加载** - 自动检测本地化插件,按需加载 STRING 文件
- 📝 **有状态编辑** - 支持批量修改、延迟保存、撤销/重做
- 🔄 **变更追踪** - 完整记录所有修改操作
- 🧪 **高可测试性** - 支持依赖注入和 mock 测试
- 📖 **完整文档** - 详尽的 API 文档和使用指南

## 📦 安装


### 作为库使用(推荐)


将以下内容添加到你的 `Cargo.toml` 文件中:

```toml
[dependencies]
esp_extractor = "0.4.0"
```

### 作为命令行工具


```bash
cargo install esp_extractor --features cli
```

或者从源码构建:

```bash
git clone https://github.com/yourusername/esp-string-parser.git
cd esp-string-parser
cargo build --release --features cli
```

## 🎯 主要功能


### ESP/ESM/ESL文件处理

- 字符串提取和翻译应用
- 文件结构分析和调试
- 压缩记录支持

### 字符串文件解析

- 支持 `.STRINGS``.ILSTRINGS``.DLSTRINGS` 文件
- 自动检测文件类型和编码
- 转换为JSON格式便于处理

详细的字符串文件使用说明请参考:[STRING_FILE_USAGE.md](STRING_FILE_USAGE.md)

## 🚀 完整翻译工作流


### 第一步:提取字符串

```bash
esp_extractor -i "MyMod.esp" -o "MyMod_strings.json"
```

**输出示例:**
```
正在解析插件: "MyMod.esp"
扫描到 15 个组(包含子组)
扫描到 1250 个记录
提取到 324 个有效字符串
结果已写入: "MyMod_strings.json"

样例字符串:
1. [00012BB7|Skyrim.esm] WEAP FULL: "Iron Sword"
2. [00013BB9|MyMod.esp] NPC_ FULL: "神秘商人"
...
```

### 第二步:编辑翻译
直接修改JSON文件中的 `original_text` 字段为翻译文本:

**修改前:**
```json
{
    "editor_id": "IronSword",
    "form_id": "00012BB7|Skyrim.esm",
    "original_text": "Iron Sword",
    "record_type": "WEAP",
    "subrecord_type": "FULL",
}
```

**修改后:**
```json
{
  "editor_id": "IronSword",
  "form_id": "00012BB7|Skyrim.esm", 
  "original_text": "铁剑",
  "record_type": "WEAP",
  "subrecord_type": "FULL"
}
```

### 第三步:应用翻译


#### 方法一:完整文件翻译

```bash
esp_extractor -i "Data/MyMod.esp" --apply-file "MyMod_CN.json" -o "Data/MyMod_CN.esp"
```

#### 方法二:部分对象翻译(推荐)

```bash
esp_extractor -i "Data/MyMod.esp" --apply-jsonstr '[{"editor_id":"IronSword","form_id":"00012BB7|Skyrim.esm","original_text":"铁剑","record_type":"WEAP","subrecord_type":"FULL"}]' -o "Data/MyMod_CN.esp"
```

#### 方法三:从标准输入读取翻译

```bash
cat MyMod_translations.json | esp_extractor -i "Data/MyMod.esp" --apply-partial-stdin -o "Data/MyMod_CN.esp"
```

#### 方法四:自动命名输出文件(覆盖原文件)

```bash
esp_extractor -i "Data/MyMod.esp" --apply-file "MyMod_CN.json"
# 输出: MyMod.esp (覆盖原文件,会自动创建备份)


esp_extractor -i "Data/MyMod.esp" --apply-jsonstr '[...]'
# 输出: MyMod.esp (覆盖原文件,会自动创建备份)

```

**翻译应用输出示例:**
```
准备应用 1 个翻译条目
翻译条目 1: [00012BB7|Skyrim.esm] WEAP FULL -> "铁剑"
翻译应用完成,输出到: "MyMod_CN.esp"
```

## ⚙️ 命令行选项


### 通用选项

- `-i, --input <FILE>`: 输入文件路径(ESP/ESM/ESL或字符串文件)(必需)
- `-o, --output <FILE>`: 输出文件路径 (可选)
- `--stats`: 显示文件统计信息
- `--quiet`: 静默模式

### ESP文件提取模式

- `--include-localized`: 包含本地化字符串(显示为StringID)
- `--unfiltered`: 包含所有字符串,跳过智能过滤

### 字符串文件解析模式

- `--parse-strings <FILE>`: 明确指定解析字符串文件(也可以通过文件扩展名自动检测)

### 翻译应用模式

- `--apply-file <JSON_FILE>`: 从JSON文件应用翻译到ESP文件
- `--apply-jsonstr <JSON_STRING>`: 从JSON字符串应用指定的翻译对象
- `--apply-partial-stdin`: 从标准输入读取JSON翻译对象

### 测试和调试模式

- `--test-rebuild`: 测试模式,解析文件后直接重建(不做任何修改),用于验证解析和重建逻辑
- `--compare-files <ESP_FILE>`: 对比两个ESP文件的结构差异

## 📋 使用示例


### ESP文件字符串提取

```bash
# 提取字符串到JSON文件

esp_extractor -i "Data/MyMod.esp"

# 指定输出文件名

esp_extractor -i "Data/MyMod.esp" -o "translations.json"
```

### 字符串文件解析

```bash
# 解析字符串文件(自动检测)

esp_extractor -i "Dragonborn_english.ILSTRINGS"

# 明确指定解析字符串文件

esp_extractor --parse-strings "Dragonborn_english.ILSTRINGS" -o "dragonborn_strings.json"

# 查看字符串文件统计信息

esp_extractor -i "Dragonborn_english.ILSTRINGS" --stats
```

### 显示统计信息

```bash
esp_extractor -i "Data/Skyrim.esm" --stats
```

### 包含特殊情况

```bash
# 包含本地化字符串

esp_extractor -i "Data/MyMod.esp" --include-localized

# 包含所有字符串(不过滤)

esp_extractor -i "Data/MyMod.esp" --unfiltered
```

### 应用翻译

```bash
# 完整文件翻译

esp_extractor -i "Data/MyMod.esp" --apply-file "MyMod_CN.json" -o "Data/MyMod_CN.esp"

# JSON字符串翻译(推荐用于少量翻译)

esp_extractor -i "Data/MyMod.esp" --apply-jsonstr '[{"editor_id":"IronSword","form_id":"00012BB7|Skyrim.esm","original_text":"铁剑","record_type":"WEAP","subrecord_type":"FULL"}]' -o "Data/MyMod_CN.esp"

# 从标准输入读取翻译

cat MyMod_translations.json | esp_extractor -i "Data/MyMod.esp" --apply-partial-stdin -o "Data/MyMod_CN.esp"

# 自动命名输出文件(覆盖原文件)

esp_extractor -i "Data/MyMod.esp" --apply-file "MyMod_CN.json"
# 输出: MyMod.esp (覆盖原文件,会自动创建备份)


esp_extractor -i "Data/MyMod.esp" --apply-jsonstr '[...]'
# 输出: MyMod.esp (覆盖原文件,会自动创建备份)

```

### 测试文件重建

```bash
# 测试解析和重建逻辑(用于调试)

esp_extractor -i "Data/MyMod.esp" --test-rebuild

# 指定输出文件

esp_extractor -i "Data/MyMod.esp" --test-rebuild -o "MyMod_test.esp"
```

### 文件结构对比

```bash
# 对比两个ESP文件的结构差异

esp_extractor -i "Data/MyMod_Original.esp" --compare-files "Data/MyMod_Modified.esp"

# 静默模式对比(仅显示差异)

esp_extractor -i "Data/MyMod_Original.esp" --compare-files "Data/MyMod_Modified.esp" --quiet
```

## 📄 输出格式


程序输出 JSON 格式的字符串数组:

```json
{
  "editor_id": "IronSword",
  "form_id": "00012BB7|Skyrim.esm", 
  "original_text": "Iron Sword",
  "record_type": "WEAP",
  "subrecord_type": "FULL"
}
```

### 字段说明

- `editor_id`: 编辑器ID
- `form_id`: FormID|主文件名  
- `original_text`: 原始文本(提取时为原文,应用翻译时修改为译文)
- `record_type`: 记录类型
- `subrecord_type`: 子记录类型

### 🔑 匹配机制

应用翻译时使用三重匹配确保精确性:
- `editor_id` + `form_id` + `record_type + " " + subrecord_type` 
- 这避免了不同对象共享相同ID时的冲突

## 🎮 支持的记录类型


- **WEAP** (武器): FULL, DESC
- **ARMO** (装备): FULL, DESC  
- **NPC_** (NPC): FULL, SHRT
- **BOOK** (书籍): FULL, DESC, CNAM
- **QUST** (任务): FULL, CNAM, NNAM
- **INFO** (对话): NAM1, RNAM
- **DIAL** (对话主题): FULL
- **MESG** (消息): DESC, FULL, ITXT
- 以及更多... (详见 `data/string_records.json`)

## 🔍 字符串过滤规则


**自动过滤的内容:**
- 空字符串
- 驼峰命名变量 (`MyVariable`)
- 下划线命名变量 (`my_variable`)
- 黑名单文本 (`<p>`)
- 控制字符

**支持的字符:**
- ✅ 所有Unicode可打印字符(中文、日文、韩文等)
- ✅ 英文字符和数字
- ✅ 标点符号和空格

## 💡 翻译工作流最佳实践


### 1. 高效翻译建议

- **使用部分翻译**:只翻译需要的条目,减少文件大小
- **三重匹配验证**:确保 `editor_id + form_id + record_type + " " + subrecord_type` 匹配正确
- **自动备份**:程序会自动创建 `.bak` 备份文件
- 使用翻译工具(如 ChatGPT、DeepL)处理大量文本
- 保持游戏术语的一致性

### 2. 部分翻译工作流

```bash
# 1. 提取所有字符串

esp_extractor -i "MyMod.esp" -o "all_strings.json"

# 2. 选择需要翻译的条目,复制到单独文件或直接使用

# 3. 修改 original_text 字段为翻译文本

# 4. 应用翻译

esp_extractor -i "MyMod.esp" --apply-jsonstr '[翻译的JSON对象]' -o "MyMod_CN.esp"

# 或者从文件应用

esp_extractor -i "MyMod.esp" --apply-file "selected_translations.json" -o "MyMod_CN.esp"

# 或者从标准输入应用(适合脚本处理)

cat selected_translations.json | esp_extractor -i "MyMod.esp" --apply-partial-stdin -o "MyMod_CN.esp"
```

### 3. 质量控制

- 翻译完成后在游戏中测试
- 检查特殊字符是否正确显示
- 验证格式字符串是否保留
- 使用备份文件快速恢复

### 4. 版本管理

- 保留原始提取的JSON文件
- 程序自动创建时间戳备份文件
- 使用版本控制系统管理翻译文件

## 🛠️ 故障排除


### 常见问题


1. **"Invalid file format" 错误**
   - 确保文件是有效的 ESP/ESM/ESL 文件

2. **"Insufficient data" 错误**
   - 文件可能被截断或损坏

3. **编码问题**
   - 程序会自动尝试多种编码

4. **翻译应用失败**
   - 检查JSON文件格式
   - 确保FormID匹配

## 🎮 支持的游戏

- The Elder Scrolls V: Skyrim Special Edition

## 📄 文件格式支持


- **ESP** (Elder Scrolls Plugin)
- **ESM** (Elder Scrolls Master)
- **ESL** (Elder Scrolls Light)

## 💻 库 API 使用


### 快速开始(推荐方式)⭐


使用 `LoadedPlugin::load_auto()` 智能自动加载插件:

```rust
use esp_extractor::LoadedPlugin;

// 自动检测 LOCALIZED 标志并加载 STRING 文件
let loaded = LoadedPlugin::load_auto("MyMod.esp".into(), Some("english"))?;

// 提取字符串
let strings = loaded.extract_strings();
println!("提取到 {} 个字符串", strings.len());

// 保存到 JSON
let json = serde_json::to_string_pretty(&strings)?;
std::fs::write("strings.json", json)?;
```

### 使用编辑器 API 应用翻译


```rust
use esp_extractor::{Plugin, PluginEditor, DefaultEspWriter};

// 加载插件
let plugin = Plugin::load("MyMod.esp".into())?;

// 创建编辑器
let mut editor = PluginEditor::new(plugin);

// 应用翻译
let translations = vec![/* ExtractedString 对象 */];
editor.apply_translations(translations)?;

// 保存修改
let writer = DefaultEspWriter;
editor.save(&writer, "MyMod_CN.esp".as_ref())?;
```

### 处理本地化插件


```rust
use esp_extractor::LocalizedPluginContext;

// 显式加载本地化插件(ESP + STRING 文件)
let context = LocalizedPluginContext::load("DLC.esm".into(), "english")?;

// 访问插件和 STRING 文件
println!("插件: {}", context.plugin().get_name());
println!("STRING 文件数: {}", context.string_files().files.len());

// 提取字符串(包含 STRING 文件内容)
let strings = context.plugin().extract_strings();
```

### 三种加载方式对比


| 方式 | API | 适用场景 |
|------|-----|---------|
| **智能自动** | `LoadedPlugin::load_auto()` | 最推荐,自动检测并处理本地化 |
| **精确控制** | `Plugin::load()` | 只需要 ESP 结构,不需要 STRING 文件 |
| **明确本地化** | `LocalizedPluginContext::load()` | 确定是本地化插件,需要 STRING 文件 |

详细的加载指南请参考:[docs/plugin-loading-guide.md](docs/plugin-loading-guide.md)

## 📚 API文档


详细的API文档可以在 [docs.rs](https://docs.rs/esp_extractor) 上查看。

## 🎮 开发


### 🛠️ 构建


```bash
# 构建库

cargo build

# 构建命令行工具

cargo build --features cli

# 运行测试

cargo test

# 生成文档

cargo doc --open
```

### 📁 目录结构


```
src/
├── lib.rs                  # 库的主入口
├── main.rs                 # 命令行工具入口
├── io/                     # IO 抽象层 (v0.4.0+)
│   ├── mod.rs
│   ├── traits.rs           # Reader/Writer trait 定义
│   ├── esp_io.rs           # ESP 文件 IO 默认实现
│   └── string_file_io.rs   # STRING 文件 IO 默认实现
├── editor/                 # 编辑器层 (v0.4.0+)
│   ├── mod.rs
│   ├── delta.rs            # 变更追踪系统
│   └── plugin_editor.rs    # 有状态插件编辑器
├── plugin_loader.rs        # 智能插件加载器 (v0.4.0+)
├── localized_context.rs    # 本地化插件上下文 (v0.4.0+)
├── plugin.rs               # 插件主类
├── record.rs               # 记录解析逻辑
├── group.rs                # 组解析逻辑
├── subrecord.rs            # 子记录解析
├── string_file.rs          # STRING 文件处理
├── string_types.rs         # 字符串类型定义
├── datatypes.rs            # 基础数据类型定义
├── utils.rs                # 工具函数
└── debug.rs                # 调试工具
data/
└── string_records.json     # 字符串记录定义
docs/
└── plugin-loading-guide.md # 插件加载完整指南 (v0.4.0+)
examples/
└── basic_usage.rs          # 基本使用示例
```

## 🤝 贡献


欢迎贡献代码!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详细信息。

## 📜 许可证


本项目采用 MIT 或 Apache-2.0 双重许可证。详情请查看 [LICENSE-MIT](LICENSE-MIT) 和 [LICENSE-APACHE](LICENSE-APACHE) 文件。

## 🎉 致谢


- Bethesda Game Studios - 创造了这些出色的游戏
- ESP文件格式的逆向工程社区
- Rust社区提供的优秀库和工具