├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── Plugin.md ├── README.md ├── Usage.md ├── charset └── charset.go ├── cmd ├── font.go ├── fontCreate.go ├── fontEdit.go ├── fontExtract.go ├── image.go ├── imageExport.go ├── imageImport.go ├── pak.go ├── pakExtract.go ├── pakReplace.go ├── root.go ├── script.go ├── scriptDecompile.go └── scriptImport.go ├── czimage ├── binio.go ├── cz.go ├── cz0.go ├── cz1.go ├── cz2.go ├── cz3.go ├── cz_test.go ├── imagefix.go ├── lzw.go ├── lzw_test.go ├── testdata │ ├── 明朝32_0.bin │ └── 明朝32_0.lzw └── util.go ├── data ├── AIR.py ├── AIR.txt ├── CartagraHD.py ├── CartagraHD.txt ├── HARMONIA.py ├── HARMONIA.txt ├── KANON.py ├── KANON.txt ├── LB_EN │ └── OPCODE.txt ├── LOOPERS.py ├── LOOPERS.txt ├── LUNARiA.py ├── LUNARiA.txt ├── PlanetarianSG.py ├── PlanetarianSG.txt ├── SP.py ├── SP │ └── OPCODE.txt └── base │ ├── air.py │ ├── cartagrahd.py │ ├── harmonia.py │ ├── kanon.py │ ├── loopers.py │ ├── lunaria.py │ └── sp.py ├── font ├── font.go ├── font_test.go ├── info.go └── info_test.go ├── game ├── VM │ ├── model.go │ └── vm.go ├── api │ ├── api.go │ └── operater.go ├── engine │ ├── engine.go │ ├── function.go │ └── function2.go ├── enum │ └── enum.go ├── expr │ ├── expr.go │ ├── expr_test.go │ └── utils.go ├── game.go ├── game_test.go ├── operator │ ├── LB_EN.go │ ├── SP.go │ ├── default_operate.go │ ├── expr_operate.go │ ├── opcode.go │ ├── plugin.go │ ├── plugin_opcode.go │ ├── undefined_operate.go │ └── util.go ├── runtime │ ├── context.go │ └── global_goto.go └── util.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── pak ├── pak.go └── pak_test.go ├── script ├── entry.go ├── model.go ├── script.go └── util.go ├── utils └── utils.go └── voice └── oggpak.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.16 18 | 19 | - name: Build 20 | run: | 21 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o LuckSystem_linux . 22 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o LuckSystem_mac . 23 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o LuckSystem.exe . 24 | - name: Create zip files 25 | run: | 26 | zip -r LuckSystem_linux_x86_64.zip LuckSystem_linux Plugin.md Usage.md README.md data 27 | zip -r LuckSystem_mac_x86_64.zip LuckSystem_mac Plugin.md Usage.md README.md data 28 | zip -r LuckSystem_windows_x86_64.zip LuckSystem.exe Plugin.md Usage.md README.md data 29 | - uses: ncipollo/release-action@v1 30 | with: 31 | artifacts: "LuckSystem_linux_x86_64.zip,LuckSystem_mac_x86_64.zip,LuckSystem_windows_x86_64.zip" 32 | bodyFile: "" 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .DS_Store 17 | .vscode/ 18 | .idea/ 19 | log/ 20 | lucksystem 21 | main 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetor/LuckSystem/6e74e16ee90fbaf370a6287658fd8893d12bfb48/LICENSE -------------------------------------------------------------------------------- /Plugin.md: -------------------------------------------------------------------------------- 1 | ## 使用Plugin支持任意游戏的脚本反编译与导入 2 | 3 | 插件为非标准的Python,语法类似Python3.4,缺少大量的内置库和一些特性,基本使用没有问题 4 | 5 | ### 编写规则 6 | 可参考 [LOOPERS.py](data/LOOPERS.py) 和 [SP.py](data/SP.py) 7 | 8 | #### 解析操作函数 9 | 在脚本种定义与OPCODE同名的函数(大小写一致),即可在反编译、导入时使用脚本中的函数去解析对应的操作参数 10 | 11 | #### Init函数 12 | 在脚本中定义的`Init`函数会在最开始执行一次,一般用来调用`set_config`,也可以进行其他初始化操作 13 | 14 | #### opcode_dict全局变量 15 | 在没有载入OPCODE或者存在未知OPCODE时,反编译会将操作名输出为`0x22` `0x3A`等十六进制标记,此时可以通过`opcode_dict`来手动映射十六进制标记和操作函数名,如下: 16 | ```python 17 | opcode_dict = { 18 | '0x22': 'MESSAGE', 19 | '0x25': 'SELECT' 20 | } 21 | ``` 22 | 此时对于未知的`0x22`操作,将会使用插件中的`MESSAGE`函数进行解析 23 | 24 | ### core包内置常量 25 | ```python 26 | core.Charset_UTF8 = "UTF-8" 27 | core.Charset_Unicode = "UTF-16LE" 28 | core.Charset_SJIS = "Shift_JIS" 29 | ``` 30 | 三种字符集的特点如下: 31 | #### Charset_UTF8 32 | 1~3byte一字,固定的结尾0x00 33 | 通常英文字符串使用此编码,目前如LOOPERS等新游戏也作为表达式(expr)和PAK包文件名编码也是UTF8 34 | 35 | #### Charset_Unicode 36 | 2byte(一个uint16)一字,固定的结尾0x0000 37 | 目前绝大多数的中文、日文文本均是此编码 38 | 39 | #### Charset_SJIS 40 | 1~2byte一字,固定的结尾0x00 41 | 老游戏的表达式和PAK包编码,一般为单日语游戏使用 42 | 43 | ### core包内置函数 44 | 45 | #### set_config 46 | `set_config(expr_charset, text_charset, default_export=True)` 47 | 设置默认值 48 | 设置`expr_charset`后可通过`core.expr`获取对应设置的值,通常与PAK的文件名编码一致 49 | 设置`text_charset`后可通过`core.text`获取对应设置的值,也会成功`read_str`和`read_len_str`的默认字符集值 50 | `default_export`即为解析插件未定义OPCODE时,是否将自动解析的uint16参数导出 51 | 52 | #### read 53 | `read(export=False) -> list(int)` 54 | 以uint16的形式读取**所有**参数,若剩余一位则将以uint8形式读取 55 | 56 | #### read_uint8 57 | `read_uint8(export=False) -> int` 58 | 以uint8的形式读取一个参数 59 | 60 | #### read_uint16 61 | `read_uint16(export=False) -> int` 62 | 以uint16的形式读取一个参数 63 | 64 | #### read_uint32 65 | `read_uint32(export=False) -> int` 66 | 以uint32的形式读取一个参数 67 | 68 | #### read_str 69 | `read_str(charset=textCharset, export=True) -> str` 70 | 按指定编码读取一个字符串 71 | 注意:如确定字符串前面存在一个`uint16`为字符串长度,则需要使用`read_len_str`进行读取,否则长度错乱会导致导入出错 72 | 73 | #### read_len_str 74 | `read_len_str(charset=textCharset, export=True) -> str` 75 | 按指定编码读取一个包含长度的字符串,导入时会自动计算新的长度 76 | 77 | #### read_jump (重要) 78 | `read_jump(file='', export=True) -> int` 79 | 以uint32的形式读取一个跳转位置参数,导入时将会自动重构跳转位置 80 | 跨文件的跳转需要传入`file`参数,一般为前一个参数。跨文件跳转标记为`global233` 81 | 跨文件的`file`参数,需要使用read_str或read_len_str读取,字符集为expr,通常与PAK文件名编码一致 82 | 文件内跳转的标记为`label66` 83 | 84 | #### end (重要) 85 | `end()` 86 | 将已经进行的读取提交,必须调用,否则将无法正常导出导入 87 | 88 | #### can_read 89 | `can_read() -> bool` 90 | 判断接下来是否可以进行读取 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important 2 | This project only accepts **bug issues** and **pull requests**, and does not provide assistance in use 3 | 此项目仅接受现有功能的BUG反馈和Pull requests,不提供使用上的帮助 4 | 5 | # Luck System 6 | 7 | LucaSystem 引擎解析工具 8 | 9 | ## 使用方法:[Usage](Usage.md) 10 | ## 插件手册:[Plugin](Plugin.md) 11 | 12 | ## LucaSystem解析完成进度 13 | 14 | ### Luca Pak 封包文件 15 | 16 | - 导出完成 17 | - 导入完成 18 | - 仅支持替换文件数据 19 | 20 | ### Luca CZImage 图片文件 21 | 22 | #### CZ0 23 | 24 | - 导出完成 32位 25 | - 导入完成 32位 26 | 27 | #### CZ1 28 | 29 | - 导出完成 8位 30 | - 导入完成 8位 31 | 32 | #### CZ2 33 | 34 | - 导出完成 8位 35 | - 导入完成 8位 36 | 37 | #### CZ3 38 | 39 | - 导出完成 32位 24位 40 | - 导入完成 32位 24位 41 | 42 | #### CZ4 43 | 44 | - LucaSystemTools中完成 45 | 46 | #### CZ5 47 | 48 | - 未遇到 49 | 50 | ### Luca Script 脚本文件 51 | 52 | - 导出完成 53 | - 导入完成 54 | - ~~简单的模拟执行~~ 55 | - 支持插件扩展(gpython) 56 | - 非标准的Python,语法类似Python3.4,缺少大量的内置库和一些特性,基本使用没有问题 57 | - 插件手册 [Plugin](Plugin.md) 58 | 59 | #### 笔记 60 | 61 | 根据时间,可以LucaSystem的脚本类型分为三个版本,目前仅研究V3版本,即最新版本。LucaSystemTools支持V2版本的脚本解析 62 | 63 | | 类型 | 长度 | 名称 | 说明 | 64 | |-----|-------|-----|------------------------------------| 65 | | uint16 | 2 | len | 代码长度 | 66 | | uint8 | 1 | opcode | 指令索引 | 67 | | uint8 | 1 | flag | 一个标志,值0~3 | 68 | | []uint16 | 2 * n | data0 | 未知参数,其中n=flag(flag<3),n=2(flag==3) | 69 | | params | len -4 -2*n | params | 参数 | 70 | | uint8 | k | align | 补齐位,其中k=len%2 | 71 | 72 | ### Luca Font 字体文件 73 | 74 | - 解析完成 75 | - 能够简单使用,生成指定文本的图像 76 | - 导出完成 77 | - 导入、制作完成 78 | 79 | #### info文件 80 | 81 | - 导出完成 82 | - 导入完成 83 | 84 | ### Luca OggPak 音频封包 85 | 86 | - 导出完成 87 | 88 | ## 目前支持的游戏 89 | 1. 《LOOPERS》 Steam 90 | 2. LB_EN:《Little Busters! English Edition》 Steam 91 | 3. SP:《Summer Pockets》 Nintendo Switch 92 | 4. CartagraHD 93 | 5. KANON Steam 94 | 6. HARMONIA Steam 95 | 7. LUNARiA Steam 96 | 8. AIR Steam 97 | 9. Planetarian SG Steam 98 | 99 | ## 目前支持的指令 100 | - [Game List](data) 101 | 102 | 103 | - BATTLE (LB_EN) 104 | - EQU 105 | - EQUN 106 | - EQUV 107 | - ADD 108 | - RANDOM 109 | - IFN 110 | - IFY 111 | - GOTO 112 | - JUMP 113 | - FARCALL 114 | - GOSUB 115 | 116 | 117 | ## 更新日志 118 | 119 | ### 2.3.2 120 | - 支持 LUNARiA Steam version [@thedanill](https://github.com/thedanill) 121 | - 支持 AIR Steam version [@thedanill](https://github.com/thedanill) 122 | - 支持 Planetarian SG Steam version [@thedanill](https://github.com/thedanill) 123 | 124 | ### 2.3.1 125 | - 支持 Harmonia FULL HD Steam version [@Mishalac](https://github.com/MishaIac) 126 | 127 | ### 2.3.0 128 | - 支持 Kanon [@Mishalac](https://github.com/MishaIac) 129 | 130 | ### 2.2.3 131 | - 支持`-blacklist`命令,添加额外的脚本名黑名单 132 | 133 | ### 2.2.1 (2023.12.4) 134 | - 支持[CartagraHD](https://vndb.org/r78712)脚本导入导出(未测试) 135 | 136 | ### 2.2.0 (2023.12.3) 137 | - 支持CZ2的导入(未实际测试) 138 | 139 | ### 2.1.0 (2023.11.28) 140 | - 支持CZ2的导出 141 | 142 | ### 2023.10.7 143 | - 支持LOOPERS导入和导出(已测试) 144 | - 支持Plugin扩展以支持任意游戏 145 | - 内置SummerPockets(未测试)和LOOPERS默认Plugin插件和OPCODE 146 | - 移除模拟器相关代码 147 | 148 | 149 | ### 6.26 150 | - 完全重构cmd使用方式 151 | - 暂不支持script脚本的cmd调用 152 | - 支持24位cz3图像,修复缺少Colorblock值导致的错误 153 | - font插入新字符改为追加替换模式,总字符数增加或保持不变 154 | 155 | ### 3.15 156 | - 修复cz图像导出时alpha通道异常的问题 157 | 158 | ### 3.11 159 | - 修复script导入导出交互bug 160 | - 测试部分交互 161 | - 新增Usage文档 162 | 163 | ### 3.03 164 | - 完整的控制台交互接口(未测试) 165 | - 帮助文档 166 | 167 | ### 2.17 168 | - 统一cz、info、font、pak、script的接口 169 | - 完善测试用例 170 | 171 | ### 2.10 172 | - 统一接口规范 173 | 174 | ### 2.9 175 | - 修复script导入导出中换行、空行的问题 176 | - Merge AEBus pr 177 | - 1. Fixed situation when LuckSystem would stop parsing scripts after finding END opcode 178 | - 2. Added handling of TASK, SAYAVOICETEXT, VARSTR_SET opcodes, and fixed handling of BATTLE opcode. 179 | - 3. Added opcode names for LB_EN, changed first three opcodes to EQU, EQUN, EQUV as specified in LITBUS_WIN32.exe, added handling of these opcodes in LB_EN.go 180 | 181 | ### 1.25 182 | - 完成pak导入导出交互 183 | 184 | ### 1.22 185 | - 完成CZ1导入 186 | - 完成CZ0导出导入 187 | - 支持LB_EN BATTLE指令 188 | - 修正PAK文件ID,与脚本中的ID对应 189 | - 更换日志库为glog 190 | - 引入tui库tview 191 | 192 | ### 1.21 193 | - 完成LZW压缩 194 | - 完成图像拆分算法 195 | - 支持CZ3格式替换图像 196 | 197 | ### 2022.1.19 198 | 199 | - 支持替换pak文件内容并打包 200 | - 不支持修改文件名和增加文件 201 | - 不再以LucaSystem引擎模拟器为目标,现以替代LucaSystemTools项目为目标 202 | 203 | ### 8.13 204 | 205 | - 项目更名为LuckSystem 206 | - 目标为实现LucaSystem引擎的模拟器 207 | 208 | ### 8.12 209 | 210 | - 支持字库的加载 211 | - 字库info文件的解析与应用 212 | - 字库CZ1图像的解析 213 | - 现已支持根据文字内容,按指定字体生成文字图像 214 | 215 | ### 8.11 216 | 217 | - 支持动态加载pak中的文件 218 | - 加载pak仅加载pak文件头,内部文件需要时读取 219 | - 支持音频文件的oggpak的解包 220 | - 开始编写CZ图像解析 221 | - 完成通用lzw解压 222 | - 支持CZ3图像的加载 223 | 224 | ### 8.7 225 | 226 | - 完美支持脚本导出为文本、导入为脚本 227 | - 开始设计与编写模拟器主体 228 | 229 | ### 8.3 230 | 231 | - 支持pak文件的加载 232 | 233 | ### 8.1 234 | 235 | - 完成大部分导出模式功能 236 | - 解析文本 237 | - 合并导出参数和原脚本参数 238 | - 将文本中的数据合并到原脚本,并转为字节数据 239 | 240 | ### 7.28 241 | 242 | - 完善导出模式,支持更多指令 243 | 244 | ### 7.27 累积 245 | 246 | - 为虚拟机增加导入模式和导出模式 247 | - 导出模式:不执行引擎层代码,将脚本转为字符串并导出 248 | - 导入模式:开始设计与编写 249 | 250 | ### 7.13 251 | 252 | - 增加engine结构,即引擎层,与虚拟机做区分 253 | - 虚拟机:执行脚本内容,保存、计算变量等逻辑相关操作 254 | - 引擎:执行模拟器的显示、交互等 255 | 256 | ### 7.12 257 | 258 | - 支持表达式计算 259 | - 表达式的读取以及中缀表达式转后缀表达式 260 | - 后缀表达式的计算 261 | - 引擎中使用内置数据类型,不在使用包装数据类型 262 | 263 | ### 7.11 264 | 265 | - 重构代码结构,使用vm来处理脚本执行相关 266 | - 增加context,在执行中传递变量表等数据 267 | - 增加变量表,储存运行时变量 268 | - 优化参数的读取 269 | - 统一接口代码,虚拟机与引擎前端交互接口 270 | 271 | ### 6.30 272 | 273 | - 支持多游戏 274 | - 设计参数、函数等结构 275 | 276 | ### 6.28 277 | 278 | - 框架设计与编写 279 | - 第三方包的选择与测试 280 | - 支持LB_EN基本解析 281 | 282 | ### 计划 283 | 284 | - 支持更多LucaSystem引擎的游戏脚本解析 285 | - 完善引擎函数 286 | - 引擎层交互的初步实现 287 | -------------------------------------------------------------------------------- /Usage.md: -------------------------------------------------------------------------------- 1 | ## 使用此工具进行翻译工作时,请注意 2 | 3 | - 最好需要提取OPCODE,从可执行文件中获取。如无法提取,需要进行以下工作: 4 | - 反编译脚本,得到全为uint16的脚本文件,找出其中可能包含字符串的操作(一般特别长,并且存在连续的、较大的值) 5 | - 使用Plugin中的`opcode_dict`功能,映射为插件函数并尝试进行解析,解析出字符串并进行翻译 6 | - 此时的字符串不能进行超长,且需要使用**全角空格**(半角字符串则使用半角空格)进行填充为原始长度,否则会导致导入后的脚本无法正常使用 7 | - 如想进行任意字符串的修改,需要识别出[base](data/base)下插件文件中的跳转操作,如`IFN` `FARCALL` `JUMP`等操作,并使用`read_jump`准确的解析出跳转的类型、跳转值等 8 | - 如已经有完整的OPCODE: 9 | - 反编译脚本,得到全为uint16的脚本文件,找出其中可能包含字符串的操作(一般特别长,并且存在连续的、较大的值) 10 | - 使用Plugin进行尝试解析 11 | - 此时的字符串不能进行超长,且需要使用**全角空格**(半角字符串则使用半角空格)进行填充为原始长度,否则会导致导入后的脚本无法正常使用 12 | - 如想进行任意字符串的修改,需要解析[base](data/base)下插件文件中的跳转操作,如`IFN` `FARCALL` `JUMP`,并使用`read_jump`准确的解析出跳转的类型、跳转值等 13 | - 导入和导出必须使用同一份相同的原SCRIPT.PAK、OPCODE和插件,对插件的任何修改都需要重新进行反编译,才可以导入 14 | - 建议:编写额外的工具,从反编译后的脚本中提取需要翻译的文本,并在翻译完成过使用工具替换到反编译后的脚本中,然后再导入,防止游戏数值被意外的修改 15 | ## 使用help能获取详细指令信息 16 | 17 | ## Example 18 | ```shell 19 | # 反编译SCRIPT.PAK,额外忽略_build_time, TEST脚本 20 | lucksystem script decompile \ 21 | -s D:/Game/LOOPERS/files/SCRIPT.PAK \ 22 | -c UTF-8 \ 23 | -O data/LOOPERS.txt \ 24 | -p data/LOOPERS.py \ 25 | -o D:/Game/LOOPERS/files/Export 26 | -b _build_time,TEST 27 | 28 | # 导出CZ2图片 29 | lucksystem image export \ 30 | -i C:/Users/wetor/Desktop/Prototype/CZ2/32/明朝32 \ 31 | -o C:/Users/wetor/Desktop/Prototype/CZ2/32/明朝32.png 32 | 33 | # 反编译SCRIPT.PAK 34 | lucksystem script decompile \ 35 | -s D:/Game/LOOPERS/files/SCRIPT.PAK \ 36 | -c UTF-8 \ 37 | -O data/LOOPERS.txt \ 38 | -p data/LOOPERS.py \ 39 | -o D:/Game/LOOPERS/files/Export 40 | 41 | # lucksystem script decompile -s D:/Game/LOOPERS/LOOPERS/files/src/SCRIPT.PAK -c UTF-8 -O data/LOOPERS.txt -p data/LOOPERS.py -o D:/Game/LOOPERS/LOOPERS/files/Export 42 | 43 | # 导入修改后的反编译脚本到SCRIPT.PAK 44 | lucksystem script import \ 45 | -s D:/Game/LOOPERS/files/SCRIPT.PAK \ 46 | -c UTF-8 \ 47 | -O data/LOOPERS.txt \ 48 | -p data/LOOPERS.py \ 49 | -i D:/Game/LOOPERS/files/Export \ 50 | -o D:/Game/LOOPERS/files/Import/SCRIPT.PAK 51 | 52 | # lucksystem script import -s D:/Game/LOOPERS/LOOPERS/files/src/SCRIPT.PAK -c UTF-8 -O data/LOOPERS.txt -p data/LOOPERS.py -i D:/Game/LOOPERS/LOOPERS/files/Export -o D:/Game/LOOPERS/LOOPERS/files/Import/SCRIPT.PAK 53 | 54 | 55 | # 查看FONT.PAK文件列表 56 | lucksystem pak \ 57 | -s data/LB_EN/FONT.PAK \ 58 | -L 59 | 60 | # 提取FONT.PAK中所有文件到temp中 61 | lucksystem pak extract \ 62 | -i data/LB_EN/FONT.PAK \ 63 | -o data/LB_EN/FONT.txt \ 64 | --all data/LB_EN/temp 65 | 66 | # 提起FONT.PAK中第6个(index从零开始)个 67 | lucksystem pak extract \ 68 | -i data/LB_EN/FONT.PAK \ 69 | -o data/LB_EN/5 \ 70 | --index 5 71 | 72 | # 替换FONT.PAK中temp内对应文件 73 | lucksystem pak replace \ 74 | -s data/LB_EN/FONT.PAK \ 75 | -o data/LB_EN/FONT.out.PAK \ 76 | -i data/LB_EN/temp 77 | 78 | # 替换FONT.PAK中文件名为info32的文件 79 | lucksystem pak replace \ 80 | -s data/LB_EN/FONT.PAK \ 81 | -o data/LB_EN/FONT.out.PAK \ 82 | -i data/LB_EN/temp/info32 \ 83 | --name info32 84 | 85 | # 替换FONT.PAK中存在FONT.txt列表的文件 86 | lucksystem pak replace \ 87 | -s data/LB_EN/FONT.PAK \ 88 | -o data/LB_EN/FONT.out.PAK \ 89 | -i data/LB_EN/FONT.txt \ 90 | --list 91 | 92 | # 提取cz到png图片 93 | lucksystem image export \ 94 | -i data/LB_EN/FONT/明朝32 \ 95 | -o data/LB_EN/0.png 96 | 97 | # 导入png图片到cz 98 | lucksystem image import \ 99 | -s data/LB_EN/FONT/明朝32 \ 100 | -i data/LB_EN/0.png \ 101 | -o data/LB_EN/0.cz1 102 | 103 | # 提取32号"明朝"字体图片和字符集txt 104 | lucksystem font extract \ 105 | -s data/Other/Font/明朝32 \ 106 | -S data/Other/Font/info32 \ 107 | -o data/Other/Font/明朝32_f.png \ 108 | -O data/Other/Font/info32_f.txt 109 | 110 | # 用ttf重绘32号"明朝"字体 111 | lucksystem font edit \ 112 | -s data/LB_EN/FONT/明朝32 \ 113 | -S data/LB_EN/FONT/info32 \ 114 | -f data/Other/Font/ARHei-400.ttf \ 115 | -o data/Other/Font/明朝32 \ 116 | -O data/Other/Font/info32 \ 117 | -r 118 | 119 | # 将allchar.txt中字符追加到明朝32字体最后方 120 | lucksystem font edit \ 121 | -s data/LB_EN/FONT/明朝32 \ 122 | -S data/LB_EN/FONT/info32 \ 123 | -f data/Other/Font/ARHei-400.ttf \ 124 | -o data/Other/Font/明朝32 \ 125 | -O data/Other/Font/info32 \ 126 | -c data/Other/Font/allchar.txt \ 127 | -a 128 | 129 | # 将allchar.txt中字符替换到到明朝32字体第7106个字符处 130 | lucksystem font edit \ 131 | -s data/LB_EN/FONT/明朝32 \ 132 | -S data/LB_EN/FONT/info32 \ 133 | -f data/Other/Font/ARHei-400.ttf \ 134 | -o data/Other/Font/明朝32 \ 135 | -O data/Other/Font/info32 \ 136 | -c data/Other/Font/allchar.txt \ 137 | -i 7105 138 | 139 | ``` 140 | -------------------------------------------------------------------------------- /charset/charset.go: -------------------------------------------------------------------------------- 1 | package charset 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | 9 | "golang.org/x/text/encoding" 10 | "golang.org/x/text/encoding/ianaindex" 11 | "golang.org/x/text/transform" 12 | ) 13 | 14 | type Charset string 15 | 16 | //中文 17 | const ( 18 | GBK Charset = "GBK" 19 | GB18030 Charset = "GB18030" 20 | GB2312 Charset = "GB2312" 21 | Big5 Charset = "Big5" 22 | ) 23 | 24 | //日文 25 | const ( 26 | EUCJP Charset = "EUCJP" 27 | ISO2022JP Charset = "ISO2022JP" 28 | ShiftJIS Charset = "Shift_JIS" 29 | ) 30 | 31 | //韩文 32 | const ( 33 | EUCKR Charset = "EUCKR" 34 | ) 35 | 36 | //Unicode 37 | const ( 38 | UTF_8 Charset = "UTF-8" 39 | UTF_16 Charset = "UTF-16" 40 | UTF_16BE Charset = "UTF-16BE" 41 | UTF_16LE Charset = "UTF-16LE" 42 | Unicode Charset = "UTF-16LE" 43 | ) 44 | 45 | //其他编码 46 | const ( 47 | Macintosh Charset = "macintosh" 48 | IBM Charset = "IBM*" 49 | Windows Charset = "Windows*" 50 | ISO Charset = "ISO-*" 51 | ) 52 | 53 | var charsetAlias = map[string]string{ 54 | "HZGB2312": "HZ-GB-2312", 55 | "hzgb2312": "HZ-GB-2312", 56 | "GB2312": "HZ-GB-2312", 57 | "gb2312": "HZ-GB-2312", 58 | } 59 | 60 | func Convert(dstCharset Charset, srcCharset Charset, src []byte) (dst string, err error) { 61 | if dstCharset == srcCharset { 62 | return string(src), nil 63 | } 64 | dst = string(src) 65 | // Converting to UTF-8. 66 | if srcCharset != "UTF-8" { 67 | if e := getEncoding(srcCharset); e != nil { 68 | tmp, err := ioutil.ReadAll( 69 | transform.NewReader(bytes.NewReader(src), e.NewDecoder()), 70 | ) 71 | 72 | if err != nil { 73 | return "", fmt.Errorf("%s to utf8 failed. %v", srcCharset, err) 74 | } 75 | src = tmp 76 | } else { 77 | return dst, errors.New(fmt.Sprintf("unsupport srcCharset: %s", srcCharset)) 78 | } 79 | } 80 | // Do the converting from UTF-8 to . 81 | if dstCharset != "UTF-8" { 82 | if e := getEncoding(dstCharset); e != nil { 83 | tmp, err := ioutil.ReadAll( 84 | transform.NewReader(bytes.NewReader(src), e.NewEncoder()), 85 | ) 86 | if err != nil { 87 | return "", fmt.Errorf("utf to %s failed. %v", dstCharset, err) 88 | } 89 | dst = string(tmp) 90 | } else { 91 | return dst, errors.New(fmt.Sprintf("unsupport dstCharset: %s", dstCharset)) 92 | } 93 | } else { 94 | dst = string(src) 95 | } 96 | return dst, nil 97 | } 98 | 99 | func ToUTF8(srcCharset Charset, src []byte) (dst string, err error) { 100 | return Convert("UTF-8", srcCharset, src) 101 | } 102 | 103 | func UTF8To(dstCharset Charset, src []byte) (dst string, err error) { 104 | return Convert(dstCharset, "UTF-8", src) 105 | } 106 | 107 | func getEncoding(charset Charset) encoding.Encoding { 108 | if c, ok := charsetAlias[string(charset)]; ok { 109 | charset = Charset(c) 110 | } 111 | if e, err := ianaindex.MIB.Encoding(string(charset)); err == nil && e != nil { 112 | return e 113 | } 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /cmd/font.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // fontCmd represents the font command 14 | var fontCmd = &cobra.Command{ 15 | Use: "font", 16 | Short: "LucaSystem 字体", 17 | Long: `LucaSystem 字体 18 | 文件头为'CZ0'、'CZ1'、'CZ2'、'CZ3'、'CZ4'等,同时需要配合与文件名字号一致的info文件 19 | 如'明朝24'和'info24'为一个字体`, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | fmt.Println("font called") 22 | }, 23 | } 24 | var ( 25 | FontCzSource string // 输入 26 | FontInfoSource string // 输入 27 | FontOutput string // 输出 28 | FontInfoOutput string // 输出info或info字符集 29 | ) 30 | 31 | func init() { 32 | rootCmd.AddCommand(fontCmd) 33 | 34 | fontCmd.PersistentFlags().StringVarP(&FontCzSource, "source", "s", "", "原字体cz文件,用作输入") 35 | fontCmd.PersistentFlags().StringVarP(&FontInfoSource, "source_info", "S", "", "原字体对应字号info文件,用作输入") 36 | fontCmd.PersistentFlags().StringVarP(&FontOutput, "output", "o", "", "输出文件") 37 | 38 | fontCmd.MarkPersistentFlagRequired("source") 39 | fontCmd.MarkPersistentFlagRequired("source_info") 40 | fontCmd.MarkPersistentFlagRequired("output") 41 | fontCmd.MarkFlagsRequiredTogether("source", "source_info", "output") 42 | } 43 | -------------------------------------------------------------------------------- /cmd/fontCreate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // fontCreateCmd represents the fontCreate command 14 | var fontCreateCmd = &cobra.Command{ 15 | Use: "create", 16 | Short: "暂不支持", 17 | Long: ``, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | fmt.Println("fontCreate called") 20 | }, 21 | } 22 | 23 | func init() { 24 | fontCmd.AddCommand(fontCreateCmd) 25 | 26 | // Here you will define your flags and configuration settings. 27 | 28 | // Cobra supports Persistent Flags which will work for this command 29 | // and all subcommands, e.g.: 30 | // fontCreateCmd.PersistentFlags().String("foo", "", "A help for foo") 31 | 32 | // Cobra supports local flags which will only run when this command 33 | // is called directly, e.g.: 34 | // fontCreateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 35 | } 36 | -------------------------------------------------------------------------------- /cmd/fontEdit.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "lucksystem/font" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // fontEditCmd represents the fontEdit command 17 | var fontEditCmd = &cobra.Command{ 18 | Use: "edit", 19 | Short: "修改或重构字体", 20 | Long: `同时修改字体图像以及对应的info`, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | fmt.Println("fontEdit called") 23 | if len(FontTTFInput) == 0 { 24 | fmt.Println("Error: required flag(s) \"input_ttf\" not set") 25 | return 26 | } 27 | f := font.LoadLucaFontFile(FontInfoSource, FontCzSource) 28 | out, err := os.Create(FontOutput) 29 | if err != nil { 30 | glog.Fatalln(err) 31 | } 32 | defer out.Close() 33 | ttf, err := os.Open(FontTTFInput) 34 | if err != nil { 35 | glog.Fatalln(err) 36 | } 37 | defer ttf.Close() 38 | if FontAppend { 39 | FontStartIndex = -1 40 | } 41 | err = f.Import(ttf, FontStartIndex, FontRedraw, FontCharsetInput) 42 | if err != nil { 43 | glog.Fatalln(err) 44 | } 45 | var outInfo *os.File = nil 46 | if len(FontInfoOutput) > 0 { 47 | outInfo, err = os.Create(FontInfoOutput) 48 | if err != nil { 49 | glog.Fatalln(err) 50 | } 51 | } 52 | err = f.Write(out, outInfo) 53 | if err != nil { 54 | glog.Fatalln(err) 55 | } 56 | 57 | }, 58 | } 59 | var ( 60 | FontTTFInput string // ttf字体文件 61 | FontCharsetInput string // 替换或追加的字符集 62 | FontRedraw bool // 重绘 63 | FontAppend bool // 追加到最后 64 | FontStartIndex int // 替换或者重绘的序号,从零开始 65 | 66 | ) 67 | 68 | func init() { 69 | fontCmd.AddCommand(fontEditCmd) 70 | fontEditCmd.Flags().StringVarP(&FontInfoOutput, "output_info", "O", "", "修改后字体info保存位置") 71 | 72 | fontEditCmd.Flags().StringVarP(&FontTTFInput, "input_ttf", "f", "", "绘制字符使用的TTF字体") 73 | fontEditCmd.Flags().StringVarP(&FontCharsetInput, "input_charset", "c", "", "增加或替换的字符集文本文件") 74 | fontEditCmd.Flags().BoolVarP(&FontAppend, "append", "a", false, "字符集绘制并添加到原字体最后") 75 | 76 | fontEditCmd.Flags().IntVarP(&FontStartIndex, "index", "i", 0, "字符集绘制并添加到的位置,从0开始") 77 | fontEditCmd.Flags().BoolVarP(&FontRedraw, "redraw", "r", false, "重绘原字体图片") 78 | fontEditCmd.MarkFlagsMutuallyExclusive("append", "index") 79 | } 80 | -------------------------------------------------------------------------------- /cmd/fontExtract.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "lucksystem/font" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // fontExtractCmd represents the fontExtract command 17 | var fontExtractCmd = &cobra.Command{ 18 | Use: "extract", 19 | Short: "提取字体图像和info", 20 | Long: `LucaSystem font文件 21 | 为FONT.PAK内容,字体为cz图像,并对应一个字号的info文件,如"明朝32"和"info32"为明朝32号字体 22 | 输出为png图像,info输出为txt字符集,对应png图像中的全字符`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | fmt.Println("fontExtract called") 25 | f := font.LoadLucaFontFile(FontInfoSource, FontCzSource) 26 | out, err := os.Create(FontOutput) 27 | if err != nil { 28 | glog.Fatalln(err) 29 | } 30 | defer out.Close() 31 | err = f.Export(out, FontInfoOutput) 32 | if err != nil { 33 | glog.Fatalln(err) 34 | } 35 | }, 36 | } 37 | 38 | func init() { 39 | fontCmd.AddCommand(fontExtractCmd) 40 | fontExtractCmd.Flags().StringVarP(&FontInfoOutput, "output_info", "O", "", "提取info中的字符集txt保存路径") 41 | 42 | } 43 | -------------------------------------------------------------------------------- /cmd/image.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // imageCmd represents the image command 13 | var imageCmd = &cobra.Command{ 14 | Use: "image", 15 | Short: "LucaSystem cz图像", 16 | Long: `LucaSystem cz图像 17 | 文件头为'CZ0'、'CZ1'、'CZ2'、'CZ3'、'CZ4'等 18 | 目前实现'CZ0'、'CZ1'、'CZ3'和CZ2的导出`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | fmt.Println("image called") 21 | }, 22 | } 23 | var ( 24 | CzInput string // 输入 25 | CzOutput string // 输出 26 | CzSource string // 原文件 27 | ) 28 | 29 | func init() { 30 | rootCmd.AddCommand(imageCmd) 31 | 32 | imageCmd.PersistentFlags().StringVarP(&CzSource, "source", "s", "", "原cz文件名") 33 | imageCmd.PersistentFlags().StringVarP(&CzInput, "input", "i", "", "输入文件") 34 | imageCmd.PersistentFlags().StringVarP(&CzOutput, "output", "o", "", "输出文件") 35 | 36 | imageCmd.MarkPersistentFlagRequired("input") 37 | imageCmd.MarkPersistentFlagRequired("output") 38 | imageCmd.MarkFlagsRequiredTogether("output", "input") 39 | } 40 | -------------------------------------------------------------------------------- /cmd/imageExport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "lucksystem/czimage" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // imageExportCmd represents the imageExport command 17 | var imageExportCmd = &cobra.Command{ 18 | Use: "export", 19 | Short: "提取cz文件到png图片", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | fmt.Println("imageExport called") 22 | cz := czimage.LoadCzImageFile(CzInput) 23 | out, err := os.Create(CzOutput) 24 | if err != nil { 25 | glog.Fatalln(err) 26 | } 27 | defer out.Close() 28 | err = cz.Export(out) 29 | if err != nil { 30 | glog.Fatalln(err) 31 | } 32 | }, 33 | } 34 | 35 | func init() { 36 | imageCmd.AddCommand(imageExportCmd) 37 | 38 | // Here you will define your flags and configuration settings. 39 | 40 | // Cobra supports Persistent Flags which will work for this command 41 | // and all subcommands, e.g.: 42 | // imageExportCmd.PersistentFlags().String("foo", "", "A help for foo") 43 | 44 | // Cobra supports local flags which will only run when this command 45 | // is called directly, e.g.: 46 | // imageExportCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 47 | } 48 | -------------------------------------------------------------------------------- /cmd/imageImport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "lucksystem/czimage" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // imageImportCmd represents the imageImport command 17 | var imageImportCmd = &cobra.Command{ 18 | Use: "import", 19 | Short: "导入png图像到cz文件中", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | fmt.Println("imageImport called") 22 | cz := czimage.LoadCzImageFile(CzSource) 23 | out, err := os.Create(CzOutput) 24 | if err != nil { 25 | glog.Fatalln(err) 26 | } 27 | defer out.Close() 28 | 29 | f, err := os.Open(CzInput) 30 | if err != nil { 31 | return 32 | } 33 | defer f.Close() 34 | err = cz.Import(f, Fill) 35 | if err != nil { 36 | glog.Fatalln(err) 37 | } 38 | err = cz.Write(out) 39 | if err != nil { 40 | glog.Fatalln(err) 41 | } 42 | }, 43 | } 44 | 45 | var ( 46 | Fill bool // 填充为原大小,仅cz1支持 47 | ) 48 | 49 | func init() { 50 | imageCmd.AddCommand(imageImportCmd) 51 | imageImportCmd.Flags().BoolVarP(&Fill, "fill", "f", false, "图像尺寸填充为与source一致,仅支持cz1") 52 | // Here you will define your flags and configuration settings. 53 | 54 | // Cobra supports Persistent Flags which will work for this command 55 | // and all subcommands, e.g.: 56 | // imageImportCmd.PersistentFlags().String("foo", "", "A help for foo") 57 | 58 | // Cobra supports local flags which will only run when this command 59 | // is called directly, e.g.: 60 | // imageImportCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 61 | } 62 | -------------------------------------------------------------------------------- /cmd/pak.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "lucksystem/charset" 10 | "lucksystem/pak" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | // pakCmd represents the pak command 16 | var pakCmd = &cobra.Command{ 17 | Use: "pak", 18 | Short: "LucaSystem pak文件", 19 | Long: `LucaSystem pak文件 20 | 无具体文件头,确定是LucaSystem引擎的游戏,文件名为大写的***.PAK`, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | if List { 23 | if len(PakSource) == 0 { 24 | fmt.Println("Error: required flag(s) \"source\" not set") 25 | return 26 | } 27 | fmt.Println("index,id,offset,size,name") 28 | p := pak.LoadPak(PakSource, charset.Charset(Charset)) 29 | for i, f := range p.Files { 30 | fmt.Printf("%d,%d,%d,%d,%s\n", i, p.NameMap[f.Name], f.Offset, f.Length, f.Name) 31 | } 32 | } 33 | }, 34 | } 35 | var ( 36 | List bool // 列表模式 37 | Charset string // 编码 38 | PakInput string // 输入 39 | PakOutput string // 输出 40 | PakSource string // 原文件 41 | ) 42 | 43 | func init() { 44 | rootCmd.AddCommand(pakCmd) 45 | 46 | pakCmd.Flags().BoolVarP(&List, "list", "L", false, "查看文件列表,需要source") 47 | pakCmd.PersistentFlags().StringVarP(&Charset, "charset", "c", string(charset.UTF_8), "字符串编码") 48 | 49 | pakCmd.PersistentFlags().StringVarP(&PakSource, "source", "s", "", "原Pak文件名") 50 | pakCmd.PersistentFlags().StringVarP(&PakInput, "input", "i", "", "输入文件或文件夹") 51 | pakCmd.PersistentFlags().StringVarP(&PakOutput, "output", "o", "", "输出文件或文件夹") 52 | 53 | pakCmd.MarkFlagsRequiredTogether("output", "input") 54 | } 55 | -------------------------------------------------------------------------------- /cmd/pakExtract.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "github.com/spf13/cobra" 11 | "lucksystem/charset" 12 | "lucksystem/pak" 13 | "os" 14 | ) 15 | 16 | // pakExtractCmd represents the pakExtract command 17 | var pakExtractCmd = &cobra.Command{ 18 | Use: "extract", 19 | Short: "解包Pak文件", 20 | Long: `LucaSystem pak文件 21 | 无具体文件头,确定是LucaSystem引擎的游戏,文件名为大写的***.PAK`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | fmt.Println("pakExtract called") 24 | p := pak.LoadPak(PakInput, charset.Charset(Charset)) 25 | out, err := os.Create(PakOutput) 26 | if err != nil { 27 | glog.Fatalln(err) 28 | } 29 | defer out.Close() 30 | if len(PakAll) > 0 { 31 | err := p.Export(out, "all", PakAll) 32 | if err != nil { 33 | glog.Fatalln(err) 34 | } 35 | } else if PakIndex >= 0 { 36 | err := p.Export(out, "index", PakIndex) 37 | if err != nil { 38 | glog.Fatalln(err) 39 | } 40 | } else if PakId >= 0 { 41 | err := p.Export(out, "id", PakId) 42 | if err != nil { 43 | glog.Fatalln(err) 44 | } 45 | } else if len(PakName) > 0 { 46 | err := p.Export(out, "name", PakName) 47 | if err != nil { 48 | glog.Fatalln(err) 49 | } 50 | } else { 51 | fmt.Println("Error: required flag(s) \"all\" or \"index\" or \"id\" or \"name\" not set") 52 | } 53 | 54 | }, 55 | } 56 | 57 | var ( 58 | PakAll string 59 | PakIndex int 60 | PakId int 61 | PakName string 62 | ) 63 | 64 | func init() { 65 | pakCmd.AddCommand(pakExtractCmd) 66 | 67 | pakExtractCmd.Flags().StringVarP(&PakAll, "all", "a", "", "提取所有文件保存到文件夹") 68 | pakExtractCmd.Flags().IntVarP(&PakIndex, "index", "x", -1, "提取指定位置文件,从0开始") 69 | pakExtractCmd.Flags().IntVarP(&PakId, "id", "d", -1, "提取指定ID文件") 70 | pakExtractCmd.Flags().StringVarP(&PakName, "name", "n", "", "提取指定文件名文件") 71 | 72 | pakExtractCmd.MarkFlagsMutuallyExclusive("all", "index", "id", "name") 73 | } 74 | -------------------------------------------------------------------------------- /cmd/pakReplace.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "github.com/golang/glog" 10 | "lucksystem/charset" 11 | "lucksystem/pak" 12 | "os" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | // pakReplaceCmd represents the pakReplace command 18 | var pakReplaceCmd = &cobra.Command{ 19 | Use: "replace", 20 | Short: "替换Pak子文件", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | fmt.Println("pakReplace called") 23 | if len(PakSource) == 0 { 24 | fmt.Println("Error: required flag(s) \"source\" not set") 25 | return 26 | } 27 | p := pak.LoadPak(PakSource, charset.Charset(Charset)) 28 | out, err := os.Create(PakOutput) 29 | if err != nil { 30 | glog.Fatalln(err) 31 | } 32 | defer out.Close() 33 | if fi, err := os.Stat(PakInput); err != nil { 34 | glog.Fatalln(err) 35 | } else if fi.IsDir() { 36 | err := p.Import(nil, "dir", PakInput) 37 | if err != nil { 38 | glog.Fatalln(err) 39 | } 40 | } else { 41 | f, err := os.Open(PakInput) 42 | if err != nil { 43 | glog.Fatalln(err) 44 | } 45 | defer f.Close() 46 | if PakList { 47 | err := p.Import(f, "list", nil) 48 | if err != nil { 49 | glog.Fatalln(err) 50 | } 51 | } else if PakId >= 0 { 52 | err := p.Import(f, "file", PakId) 53 | if err != nil { 54 | glog.Fatalln(err) 55 | } 56 | } else if len(PakName) > 0 { 57 | err := p.Import(f, "file", PakName) 58 | if err != nil { 59 | glog.Fatalln(err) 60 | } 61 | } 62 | 63 | } 64 | err = p.Write(out) 65 | if err != nil { 66 | glog.Fatalln(err) 67 | } 68 | }, 69 | } 70 | 71 | var ( 72 | PakList bool 73 | ) 74 | 75 | func init() { 76 | pakCmd.AddCommand(pakReplaceCmd) 77 | 78 | pakReplaceCmd.Flags().BoolVarP(&PakList, "list", "l", false, "input是否为列表文件") 79 | pakReplaceCmd.Flags().IntVarP(&PakId, "id", "d", -1, "替换指定ID文件") 80 | pakReplaceCmd.Flags().StringVarP(&PakName, "name", "n", "", "替换指定文件名") 81 | 82 | pakReplaceCmd.MarkFlagsMutuallyExclusive("list", "id", "name") 83 | } 84 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 WeTor wetorx@qq.com 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "flag" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/go-restruct/restruct" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // rootCmd represents the base command when called without any subcommands 17 | var rootCmd = &cobra.Command{ 18 | Use: "LuckSystem", 19 | Version: "2.0.3", 20 | Short: "LucaSystem引擎工具集", 21 | Long: `LucaSystem引擎工具集 22 | https://github.com/wetor/LuckSystem 23 | wetor(wetorx@qq.com)`, 24 | // Uncomment the following line if your bare application 25 | // has an action associated with it: 26 | // Run: func(cmd *cobra.Command, args []string) { }, 27 | } 28 | 29 | // Execute adds all child commands to the root command and sets flags appropriately. 30 | // This is called by main.main(). It only needs to happen once to the rootCmd. 31 | func Execute() { 32 | 33 | if Log { 34 | flag.Set("alsologtostderr", "true") 35 | flag.Set("log_dir", LogDir) 36 | flag.Set("v", strconv.Itoa(LogLevel)) 37 | flag.Parse() 38 | } 39 | err := rootCmd.Execute() 40 | if err != nil { 41 | os.Exit(1) 42 | } 43 | 44 | } 45 | 46 | var ( 47 | Log bool 48 | LogLevel int 49 | LogDir string 50 | ) 51 | 52 | func init() { 53 | if len(os.Args) <= 1 || os.Args[1] == "-h" || os.Args[1] == "--help" { 54 | _ = rootCmd.Help() 55 | os.Exit(1) 56 | } 57 | 58 | restruct.EnableExprBeta() 59 | 60 | rootCmd.Flags().BoolVar(&Log, "log", true, "启用日志") 61 | rootCmd.Flags().IntVar(&LogLevel, "log_level", 5, "输出日志等级") 62 | rootCmd.Flags().StringVar(&LogDir, "log_dir", "log", "保存日志路径") 63 | } 64 | -------------------------------------------------------------------------------- /cmd/script.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | "lucksystem/charset" 11 | ) 12 | 13 | // scriptCmd represents the script command 14 | var scriptCmd = &cobra.Command{ 15 | Use: "script", 16 | Short: "LucaSystem script文件", 17 | Long: `LucaSystem script文件 18 | 无具体文件头,确定是LucaSystem引擎的游戏,SCRIPT.PAK中的文件即为script文件 19 | 其中'_VARNUM'、'_CGMODE'、'_SCR_LABEL'、'_VOICE_PARAM'、'_BUILD_COUNT'、'_TASK'等文件不支持单个解析`, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | fmt.Println("script called") 22 | }, 23 | } 24 | 25 | var ( 26 | ScriptOpcode string 27 | ScriptPlugin string 28 | ScriptSource string 29 | ScriptExportDir string 30 | ScriptImportDir string 31 | ScriptImportOutput string 32 | ScriptNoSubDir bool 33 | ScriptBlackList string 34 | ) 35 | 36 | func init() { 37 | rootCmd.AddCommand(scriptCmd) 38 | 39 | scriptCmd.PersistentFlags().StringVarP(&ScriptSource, "source", "s", "SCRIPT.PAK", "SCRIPT.PAK文件") 40 | scriptCmd.PersistentFlags().StringVarP(&Charset, "charset", "c", string(charset.UTF_8), "PAK文件字符串编码") 41 | scriptCmd.PersistentFlags().StringVarP(&ScriptBlackList, "blacklist", "b", "", "额外的脚本黑名单") 42 | scriptCmd.PersistentFlags().StringVarP(&ScriptOpcode, "opcode", "O", "", "游戏的OPCODE文件") 43 | scriptCmd.PersistentFlags().StringVarP(&ScriptPlugin, "plugin", "p", "", "游戏OPCODE解析插件") 44 | scriptCmd.PersistentFlags().BoolVarP(&ScriptNoSubDir, "no_subdir", "n", false, "输入和输出路径的不追加 '/SCRIPT.PAK/' 子目录") 45 | scriptCmd.MarkPersistentFlagRequired("source") 46 | } 47 | -------------------------------------------------------------------------------- /cmd/scriptDecompile.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/go-restruct/restruct" 11 | "lucksystem/charset" 12 | "lucksystem/game" 13 | "lucksystem/game/enum" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | // scriptDecompileCmd represents the scriptDecompileCmd command 19 | var scriptDecompileCmd = &cobra.Command{ 20 | Use: "decompile", 21 | Short: "反编译脚本", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | fmt.Println("scriptExtract called") 24 | restruct.EnableExprBeta() 25 | game.ScriptBlackList = append(game.ScriptBlackList, strings.Split(ScriptBlackList, ",")...) 26 | g := game.NewGame(&game.GameOptions{ 27 | GameName: "Custom", 28 | PluginFile: ScriptPlugin, 29 | OpcodeFile: ScriptOpcode, 30 | Coding: charset.Charset(Charset), 31 | Mode: enum.VMRunExport, 32 | }) 33 | g.LoadScriptResources(ScriptSource) 34 | g.RunScript() 35 | 36 | g.ExportScript(ScriptExportDir, ScriptNoSubDir) 37 | }, 38 | } 39 | 40 | func init() { 41 | scriptCmd.AddCommand(scriptDecompileCmd) 42 | 43 | scriptDecompileCmd.Flags().StringVarP(&ScriptExportDir, "output", "o", "output", "反编译输出路径") 44 | 45 | // Here you will define your flags and configuration settings. 46 | 47 | // Cobra supports Persistent Flags which will work for this command 48 | // and all subcommands, e.g.: 49 | // imageExportCmd.PersistentFlags().String("foo", "", "A help for foo") 50 | 51 | // Cobra supports local flags which will only run when this command 52 | // is called directly, e.g.: 53 | // imageExportCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 54 | } 55 | -------------------------------------------------------------------------------- /cmd/scriptImport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/go-restruct/restruct" 10 | "lucksystem/charset" 11 | "lucksystem/game" 12 | "lucksystem/game/enum" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | // scriptImportCmd represents the scriptImportCmd command 18 | var scriptImportCmd = &cobra.Command{ 19 | Use: "import", 20 | Short: "导入反编译的脚本", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | restruct.EnableExprBeta() 23 | game.ScriptBlackList = append(game.ScriptBlackList, strings.Split(ScriptBlackList, ",")...) 24 | g := game.NewGame(&game.GameOptions{ 25 | GameName: "Custom", 26 | PluginFile: ScriptPlugin, 27 | OpcodeFile: ScriptOpcode, 28 | Coding: charset.Charset(Charset), 29 | Mode: enum.VMRunImport, 30 | }) 31 | g.LoadScriptResources(ScriptSource) 32 | g.ImportScript(ScriptImportDir, ScriptNoSubDir) 33 | g.RunScript() 34 | g.ImportScriptWrite(ScriptImportOutput) 35 | 36 | }, 37 | } 38 | 39 | func init() { 40 | scriptCmd.AddCommand(scriptImportCmd) 41 | 42 | scriptImportCmd.Flags().StringVarP(&ScriptImportDir, "input", "i", "output", "输出的反编译脚本路径") 43 | scriptImportCmd.Flags().StringVarP(&ScriptImportOutput, "output", "o", "SCRIPT.PAK.out", "输出的SCRIPT.PAK文件") 44 | 45 | scriptImportCmd.MarkFlagsRequiredTogether("input", "output") 46 | } 47 | -------------------------------------------------------------------------------- /czimage/binio.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | type BitIO struct { 8 | data []byte 9 | byteOffset int 10 | byteSize int 11 | bitOffset int 12 | bitSize int 13 | } 14 | 15 | func NewBitIO(data []byte) *BitIO { 16 | return &BitIO{ 17 | data: data, 18 | } 19 | } 20 | 21 | func (b *BitIO) ByteOffset() int { 22 | return b.byteOffset 23 | } 24 | 25 | func (b *BitIO) ByteSize() int { 26 | return b.byteSize 27 | } 28 | 29 | func (b *BitIO) Bytes() []byte { 30 | return b.data[:b.byteSize] 31 | } 32 | 33 | func (b *BitIO) ReadBit(bitLen int) uint64 { 34 | if bitLen > 8*8 { 35 | panic("不支持,最多64位") 36 | } 37 | if bitLen%8 == 0 && b.bitOffset == 0 { 38 | return b.Read(bitLen / 8) 39 | } 40 | var result uint64 41 | for i := 0; i < bitLen; i++ { 42 | // 从最低位开始读取 43 | bitValue := uint64((b.data[b.byteOffset] >> uint(b.bitOffset)) & 1) 44 | b.bitOffset++ 45 | if b.bitOffset == 8 { 46 | b.byteOffset++ 47 | b.bitOffset = 0 48 | } 49 | // 将读取的位放入结果中 50 | result |= bitValue << uint(i) 51 | } 52 | //fmt.Println(b.byteOffset, b.bitOffset) 53 | return result 54 | } 55 | 56 | func (b *BitIO) Read(byteLen int) uint64 { 57 | if byteLen > 8 { 58 | panic("不支持,最多64位") 59 | } 60 | paddedSlice := make([]byte, 8) 61 | copy(paddedSlice, b.data[b.byteOffset:b.byteOffset+byteLen]) 62 | b.byteOffset += byteLen 63 | return binary.LittleEndian.Uint64(paddedSlice) 64 | } 65 | 66 | func (b *BitIO) WriteBit(data uint64, bitLen int) { 67 | if bitLen > 8*8 { 68 | panic("不支持,最多64位") 69 | } 70 | 71 | if bitLen%8 == 0 && b.bitOffset == 0 { 72 | b.Write(data, bitLen/8) 73 | return 74 | } 75 | 76 | for i := 0; i < bitLen; i++ { 77 | // 从 value 中获取要写入的位 78 | bitValue := (data >> uint(i)) & 1 79 | // 清除目标字节中的目标位 80 | b.data[b.byteOffset] &= ^(1 << uint(b.bitOffset)) 81 | // 将 bitValue 写入目标位 82 | b.data[b.byteOffset] |= byte(bitValue << uint(b.bitOffset)) 83 | 84 | b.bitOffset++ 85 | if b.bitOffset == 8 { 86 | b.byteOffset++ 87 | b.bitOffset = 0 88 | } 89 | } 90 | 91 | b.byteSize = b.byteOffset + (b.bitOffset+7)/8 92 | } 93 | 94 | func (b *BitIO) Write(data uint64, byteLen int) { 95 | if byteLen > 8 { 96 | panic("不支持,最多64位") 97 | } 98 | paddedSlice := make([]byte, 8) 99 | binary.LittleEndian.PutUint64(paddedSlice, data) 100 | copy(b.data[b.byteOffset:b.byteOffset+byteLen], paddedSlice[:byteLen]) 101 | b.byteOffset += byteLen 102 | b.byteSize = b.byteOffset + (b.bitOffset+7)/8 103 | } 104 | -------------------------------------------------------------------------------- /czimage/cz.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "encoding/binary" 5 | "image" 6 | "io" 7 | "os" 8 | 9 | "github.com/go-restruct/restruct" 10 | "github.com/golang/glog" 11 | ) 12 | 13 | // CzHeader 14 | // 15 | // Description 长度为15 byte 16 | type CzHeader struct { 17 | Magic []byte `struct:"size=4"` 18 | HeaderLength uint32 19 | Width uint16 20 | Heigth uint16 21 | Colorbits uint16 22 | Colorblock uint8 23 | } 24 | 25 | // CzData 26 | // 27 | // Description cz解析后的结构 28 | type CzData struct { 29 | Raw []byte // Load() 30 | OutputInfo *CzOutputInfo // Load() 31 | Image image.Image // Export() 32 | PngImage image.Image // Import() 33 | 34 | } 35 | 36 | // CzBlockInfo 37 | // 38 | // Description 块大小 39 | type CzBlockInfo struct { 40 | CompressedSize uint32 41 | RawSize uint32 42 | } 43 | 44 | // CzOutputInfo 45 | // 46 | // Description 文件分块信息 47 | type CzOutputInfo struct { 48 | Offset int `struct:"-"` 49 | TotalRawSize int `struct:"-"` 50 | TotalCompressedSize int `struct:"-"` 51 | FileCount uint32 52 | BlockInfo []CzBlockInfo `struct:"size=FileCount"` 53 | } 54 | 55 | type CzImage interface { 56 | Load(header CzHeader, data []byte) 57 | GetImage() image.Image 58 | Export(w io.Writer) error 59 | Import(r io.Reader, fillSize bool) error 60 | Write(w io.Writer) error 61 | } 62 | 63 | func LoadCzImageFile(file string) CzImage { 64 | data, err := os.ReadFile(file) 65 | if err != nil { 66 | glog.Fatalln(err) 67 | } 68 | return LoadCzImage(data) 69 | } 70 | func LoadCzImage(data []byte) CzImage { 71 | header := CzHeader{} 72 | err := restruct.Unpack(data[:15], binary.LittleEndian, &header) 73 | if err != nil { 74 | glog.Fatalln(err) 75 | } 76 | glog.V(6).Infoln("cz header", header) 77 | var cz CzImage 78 | switch string(header.Magic[:3]) { 79 | case "CZ0": 80 | cz = new(Cz0Image) 81 | cz.Load(header, data) 82 | case "CZ1": 83 | cz = new(Cz1Image) 84 | cz.Load(header, data) 85 | case "CZ2": 86 | cz = new(Cz2Image) 87 | cz.Load(header, data) 88 | case "CZ3": 89 | cz = new(Cz3Image) 90 | cz.Load(header, data) 91 | default: 92 | glog.Fatalln("Unknown Cz image type") 93 | } 94 | 95 | return cz 96 | } 97 | -------------------------------------------------------------------------------- /czimage/cz0.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/go-restruct/restruct" 6 | "github.com/golang/glog" 7 | "image" 8 | "image/color" 9 | "image/png" 10 | "io" 11 | ) 12 | 13 | type Cz0Header struct { 14 | Flag uint8 15 | X uint16 16 | Y uint16 17 | Width1 uint16 18 | Heigth1 uint16 19 | 20 | Width2 uint16 21 | Heigth2 uint16 22 | } 23 | 24 | // Cz0Image 25 | // Description Cz0.Load() 载入数据 26 | // Description Cz0.Export() 解压数据,转化成Image并导出 27 | // Description Cz0.GetImage() 解压数据,转化成Image 28 | type Cz0Image struct { 29 | CzHeader 30 | Cz0Header 31 | CzData 32 | } 33 | 34 | // Load 35 | // Description 载入cz图像 36 | // Receiver cz *Cz0Image 37 | // Param header CzHeader 38 | // Param data []byte 39 | // 40 | func (cz *Cz0Image) Load(header CzHeader, data []byte) { 41 | cz.CzHeader = header 42 | cz.Raw = data 43 | err := restruct.Unpack(cz.Raw[15:], binary.LittleEndian, &cz.Cz0Header) 44 | if err != nil { 45 | panic(err) 46 | } 47 | glog.V(6).Infoln("cz0 header ", cz.Cz0Header) 48 | cz.OutputInfo = nil 49 | } 50 | 51 | // decompress 52 | // Description 解压数据 53 | // Receiver cz *Cz0Image 54 | // 55 | func (cz *Cz0Image) decompress() { 56 | //os.WriteFile("../data/LB_EN/IMAGE/2.lzw", cz.Raw[int(cz.HeaderLength)+cz.OutputInfo.Offset:], 0666) 57 | glog.V(6).Infoln("size ", len(cz.Raw)) 58 | pic := image.NewNRGBA(image.Rect(0, 0, int(cz.Width), int(cz.Heigth))) 59 | offset := int(cz.HeaderLength) 60 | switch cz.Colorbits { 61 | case 32: 62 | for y := 0; y < int(cz.Heigth); y++ { 63 | for x := 0; x < int(cz.Width); x++ { 64 | pic.SetNRGBA(x, y, color.NRGBA{ 65 | R: cz.Raw[offset+0], 66 | G: cz.Raw[offset+1], 67 | B: cz.Raw[offset+2], 68 | A: cz.Raw[offset+3]}, 69 | ) 70 | offset += 4 71 | } 72 | } 73 | } 74 | cz.Image = pic 75 | } 76 | 77 | // GetImage 78 | // Description 取得解压后的图像数据 79 | // Receiver cz *Cz0Image 80 | // Return image.Image 81 | // 82 | func (cz *Cz0Image) GetImage() image.Image { 83 | if cz.Image == nil { 84 | cz.decompress() 85 | } 86 | return cz.Image 87 | } 88 | 89 | // Export 90 | // Description 导出图像到文件 91 | // Receiver cz *Cz0Image 92 | // Param w io.Writer 93 | // Return error 94 | // 95 | func (cz *Cz0Image) Export(w io.Writer) error { 96 | if cz.Image == nil { 97 | cz.decompress() 98 | } 99 | return png.Encode(w, cz.Image) 100 | } 101 | 102 | // Import 103 | // Description 导入图像 104 | // Receiver cz *Cz0Image 105 | // Param r io.Reader 106 | // Param fillSize bool 107 | // Return error 108 | // 109 | func (cz *Cz0Image) Import(r io.Reader, fillSize bool) error { 110 | var err error 111 | cz.PngImage, err = png.Decode(r) 112 | return err 113 | 114 | } 115 | 116 | // Write 117 | // Description 将图像保存为cz 118 | // Receiver cz *Cz0Image 119 | // Param w io.Writer 120 | // Return error 121 | // 122 | func (cz *Cz0Image) Write(w io.Writer) error { 123 | var err error 124 | glog.V(6).Infoln(cz.CzHeader) 125 | err = WriteStruct(w, &cz.CzHeader, &cz.Cz0Header) 126 | if err != nil { 127 | return err 128 | } 129 | pic, ok := cz.PngImage.(*image.NRGBA) 130 | if !ok { 131 | pic = ImageToNRGBA(cz.PngImage) 132 | } 133 | switch cz.Colorbits { 134 | case 32: 135 | _, err = w.Write(pic.Pix) 136 | } 137 | return err 138 | } 139 | -------------------------------------------------------------------------------- /czimage/cz1.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "io" 8 | 9 | "github.com/golang/glog" 10 | ) 11 | 12 | // Cz1Image 13 | // 14 | // Description Cz1.Load() 载入并解压数据,转化成Image 15 | type Cz1Image struct { 16 | CzHeader 17 | ColorPanel []color.NRGBA // []BGRA 18 | CzData 19 | } 20 | 21 | func (cz *Cz1Image) Load(header CzHeader, data []byte) { 22 | cz.CzHeader = header 23 | cz.Raw = data 24 | 25 | offset := int(cz.HeaderLength) 26 | if cz.Colorbits == 4 || cz.Colorbits == 8 { 27 | cz.ColorPanel = make([]color.NRGBA, 1<> 4 // high4bit 66 | } 67 | pic.SetNRGBA(x, y, cz.ColorPanel[index]) 68 | i++ 69 | } 70 | } 71 | case 8: 72 | i := 0 73 | for y := 0; y < int(cz.CzHeader.Heigth); y++ { 74 | for x := 0; x < int(cz.CzHeader.Width); x++ { 75 | pic.SetNRGBA(x, y, cz.ColorPanel[buf[i]]) 76 | i++ 77 | } 78 | } 79 | case 24: 80 | // TODO 未测试 81 | // RGB 82 | i := 0 83 | for y := 0; y < int(cz.CzHeader.Heigth); y++ { 84 | for x := 0; x < int(cz.CzHeader.Width); x++ { 85 | pic.SetNRGBA(x, y, color.NRGBA{ 86 | R: buf[i+0], 87 | G: buf[i+1], 88 | B: buf[i+2], 89 | A: 0xFF, 90 | }) 91 | i += 3 92 | } 93 | } 94 | case 32: 95 | // TODO 未测试 96 | // RGBA 97 | pic.Pix = buf 98 | } 99 | cz.Image = pic 100 | } 101 | 102 | // GetImage 103 | // 104 | // Description 取得解压后的图像数据 105 | // Receiver cz *Cz1Image 106 | // Return image.Image 107 | func (cz *Cz1Image) GetImage() image.Image { 108 | if cz.Image == nil { 109 | cz.decompress() 110 | } 111 | return cz.Image 112 | } 113 | 114 | // Export 115 | // 116 | // Description 导出图像到文件 117 | // Receiver cz *Cz1Image 118 | // Param w io.Writer 119 | // Return error 120 | func (cz *Cz1Image) Export(w io.Writer) error { 121 | if cz.Image == nil { 122 | cz.decompress() 123 | } 124 | return png.Encode(w, cz.Image) 125 | } 126 | 127 | // Import 128 | // 129 | // Description 130 | // Receiver cz *Cz1Image 131 | // Param r io.Reader 132 | // Param fillSize bool 是否填充大小 133 | // Return error 134 | func (cz *Cz1Image) Import(r io.Reader, fillSize bool) error { 135 | var err error 136 | cz.PngImage, err = png.Decode(r) 137 | if err != nil { 138 | panic(err) 139 | } 140 | pic := cz.PngImage.(*image.NRGBA) 141 | width := int(cz.Width) 142 | height := int(cz.Heigth) 143 | if fillSize == true { 144 | // 填充大小 145 | pic = FillImage(pic, width, height) 146 | } 147 | 148 | if width != pic.Rect.Size().X || height != pic.Rect.Size().Y { 149 | glog.V(2).Infof("图片大小不匹配,应该为 w%d h%d\n", width, height) 150 | return err 151 | } 152 | data := make([]byte, width*height) 153 | i := 0 154 | for y := 0; y < height; y++ { 155 | for x := 0; x < width; x++ { 156 | data[i] = pic.At(x, y).(color.NRGBA).A 157 | i++ 158 | } 159 | } 160 | blockSize := 0 161 | if len(cz.OutputInfo.BlockInfo) != 0 { 162 | blockSize = int(cz.OutputInfo.BlockInfo[0].CompressedSize) 163 | } 164 | cz.Raw, cz.OutputInfo = Compress(data, blockSize) 165 | 166 | cz.OutputInfo.TotalRawSize = 0 167 | cz.OutputInfo.TotalCompressedSize = 0 168 | for _, block := range cz.OutputInfo.BlockInfo { 169 | cz.OutputInfo.TotalRawSize += int(block.RawSize) 170 | cz.OutputInfo.TotalCompressedSize += int(block.CompressedSize) 171 | } 172 | cz.OutputInfo.Offset = 4 + int(cz.OutputInfo.FileCount)*8 173 | 174 | return nil 175 | } 176 | 177 | func (cz *Cz1Image) Write(w io.Writer) error { 178 | var err error 179 | glog.V(6).Infoln(cz.CzHeader) 180 | err = WriteStruct(w, &cz.CzHeader, cz.ColorPanel, cz.OutputInfo) 181 | 182 | if err != nil { 183 | return err 184 | } 185 | _, err = w.Write(cz.Raw) 186 | 187 | return err 188 | 189 | } 190 | -------------------------------------------------------------------------------- /czimage/cz2.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "io" 8 | 9 | "github.com/golang/glog" 10 | ) 11 | 12 | type Cz2Header struct { 13 | Unknown1 uint8 14 | Unknown2 uint8 15 | Unknown3 uint8 16 | } 17 | 18 | // Cz2Image 19 | // 20 | // Description Cz1.Load() 载入并解压数据,转化成Image 21 | type Cz2Image struct { 22 | CzHeader 23 | Cz2Header 24 | ColorPanel []color.NRGBA // []BGRA 25 | CzData 26 | } 27 | 28 | func (cz *Cz2Image) Load(header CzHeader, data []byte) { 29 | cz.CzHeader = header 30 | cz.Raw = data 31 | 32 | offset := int(cz.HeaderLength) 33 | if cz.Colorbits == 4 || cz.Colorbits == 8 { 34 | cz.ColorPanel = make([]color.NRGBA, 1<data 33 | // Param header CzHeader 34 | // Param img image.Image 35 | // Return data 36 | // 37 | func DiffLine(header CzHeader, pic *image.NRGBA) (data []byte) { 38 | width := int(header.Width) 39 | height := int(header.Heigth) 40 | 41 | if width != pic.Rect.Size().X || height != pic.Rect.Size().Y { 42 | glog.V(2).Infof("图片大小不匹配,应该为 w%d h%d\n", width, height) 43 | return nil 44 | } 45 | data = make([]byte, len(pic.Pix)) 46 | if header.Colorblock == 0 { 47 | header.Colorblock = 3 48 | glog.V(2).Infof("Colorblock(0x0E:0x10)值为零,默认为3\n") 49 | } 50 | blockHeight := int(uint16(math.Ceil(float64(height) / float64(header.Colorblock)))) 51 | pixelByteCount := int(header.Colorbits >> 3) 52 | lineByteCount := width * pixelByteCount 53 | var currLine []byte 54 | preLine := make([]byte, lineByteCount) 55 | i := 0 56 | for y := 0; y < height; y++ { 57 | currLine = pic.Pix[i : i+lineByteCount] 58 | if y%blockHeight != 0 { 59 | for x := 0; x < lineByteCount; x++ { 60 | currLine[x] -= preLine[x] 61 | // 因为是每一行较上一行的变化,故逆向执行时需要累加差异 62 | preLine[x] += currLine[x] 63 | } 64 | } else { 65 | preLine = currLine 66 | } 67 | 68 | copy(data[i:i+lineByteCount], currLine) 69 | i += lineByteCount 70 | } 71 | return data 72 | } 73 | 74 | // LineDiff 拆分图像还原 75 | // Description 拆分图像还原,cz3用 data->png 76 | // Param header *CzHeader 77 | // Param data []byte 78 | // Return image.Image 79 | // 80 | func LineDiff(header *CzHeader, data []byte) image.Image { 81 | //os.WriteFile("../data/LB_EN/IMAGE/ld.data", data, 0666) 82 | width := int(header.Width) 83 | height := int(header.Heigth) 84 | pic := image.NewNRGBA(image.Rect(0, 0, width, height)) 85 | if header.Colorblock == 0 { 86 | header.Colorblock = 3 87 | glog.V(2).Infof("Colorblock(0x0E:0x10)值为零,默认为3\n") 88 | } 89 | blockHeight := int(uint16(math.Ceil(float64(height) / float64(header.Colorblock)))) 90 | pixelByteCount := int(header.Colorbits >> 3) 91 | lineByteCount := width * pixelByteCount 92 | var currLine []byte 93 | preLine := make([]byte, lineByteCount) 94 | i := 0 95 | for y := 0; y < height; y++ { 96 | currLine = data[i : i+lineByteCount] 97 | if y%blockHeight != 0 { 98 | for x := 0; x < lineByteCount; x++ { 99 | currLine[x] += preLine[x] 100 | } 101 | } 102 | preLine = currLine 103 | if pixelByteCount == 4 { 104 | // y*pic.Stride : (y+1)*pic.Stride 105 | copy(pic.Pix[i:i+lineByteCount], currLine) 106 | } else if pixelByteCount == 3 { 107 | for x := 0; x < lineByteCount; x += 3 { 108 | pic.SetNRGBA(x/3, y, color.NRGBA{R: currLine[x], G: currLine[x+1], B: currLine[x+2], A: 0xFF}) 109 | } 110 | } 111 | i += lineByteCount 112 | } 113 | //os.WriteFile("../data/LB_EN/IMAGE/ld.data.pix", pic.Pix, 0666) 114 | return pic 115 | } 116 | -------------------------------------------------------------------------------- /czimage/lzw.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // compressLZW lzw压缩 8 | // 9 | // Description lzw压缩一块 10 | // Param data []byte 未压缩数据 11 | // Param size int 压缩后数据的大小限制 12 | // Param last string 上一个lzw压缩剩余的element 13 | // Return count 使用数据量 14 | // Return compressed 压缩后的数据 15 | // Return lastElement lzw压缩剩余的element 16 | func compressLZW(data []byte, size int, last string) (count int, compressed []uint16, lastElement string) { 17 | count = 0 18 | dictionary := make(map[string]uint16) 19 | for i := 0; i < 256; i++ { 20 | dictionary[string(byte(i))] = uint16(i) 21 | } 22 | dictionaryCount := uint16(len(dictionary) + 1) 23 | element := "" 24 | if len(last) != 0 { 25 | element = last 26 | } 27 | compressed = make([]uint16, 0, size) 28 | for _, c := range data { 29 | entry := element + string(c) 30 | if _, isTrue := dictionary[entry]; isTrue { 31 | element = entry 32 | } else { 33 | compressed = append(compressed, dictionary[element]) 34 | dictionary[entry] = dictionaryCount 35 | element = string(c) 36 | dictionaryCount++ 37 | } 38 | count++ 39 | if size > 0 && len(compressed) == size { 40 | break 41 | } 42 | } 43 | lastElement = element 44 | if len(compressed) == 0 { 45 | if len(lastElement) != 0 { 46 | // 数据在上一片压缩完毕,此次压缩无数据,剩余lastElement,拆分加入 47 | for _, c := range lastElement { 48 | compressed = append(compressed, dictionary[string(c)]) 49 | } 50 | } 51 | return count, compressed, "" 52 | } else if len(compressed) < size { 53 | // 数据压缩完毕,未达到size,则为最后一片,直接加入剩余数据 54 | if len(lastElement) != 0 { 55 | compressed = append(compressed, dictionary[lastElement]) 56 | } 57 | return count, compressed, "" 58 | } 59 | // 数据压缩完毕,达到size,剩余数据交给下一片 60 | return count, compressed, lastElement 61 | } 62 | 63 | // decompressLZW lzw解压 64 | // 65 | // Description lzw解压一块 66 | // Param compressed []uint16 压缩的数据 67 | // Param size int 未压缩数据大小,可超过 68 | // Return []byte 解压后的数据 69 | func decompressLZW(compressed []uint16, size int) []byte { 70 | 71 | dictionary := make(map[uint16][]byte) 72 | for i := 0; i < 256; i++ { 73 | dictionary[uint16(i)] = []byte{byte(i)} 74 | } 75 | dictionaryCount := uint16(len(dictionary)) 76 | w := dictionary[compressed[0]] 77 | decompressed := make([]byte, 0, size) 78 | for _, element := range compressed { 79 | var entry []byte 80 | if x, ok := dictionary[element]; ok { 81 | entry = make([]byte, len(x)) 82 | copy(entry, x) 83 | } else if element == dictionaryCount { 84 | entry = make([]byte, len(w), len(w)+1) 85 | copy(entry, w) 86 | entry = append(entry, w[0]) 87 | } else { 88 | panic(fmt.Sprintf("Bad compressed element: %d", element)) 89 | } 90 | decompressed = append(decompressed, entry...) 91 | w = append(w, entry[0]) 92 | dictionary[dictionaryCount] = w 93 | dictionaryCount++ 94 | 95 | w = entry 96 | } 97 | return decompressed 98 | } 99 | 100 | func decompressLZW2(data []byte, size int) []byte { 101 | dictionary := make(map[int][]byte) 102 | for i := 0; i < 256; i++ { 103 | dictionary[i] = []byte{byte(i)} 104 | } 105 | dictionaryCount := len(dictionary) 106 | result := make([]byte, 0, size) 107 | 108 | dataSize := len(data) 109 | data = append(data, []byte{0, 0}...) 110 | bitIO := NewBitIO(data) 111 | w := dictionary[0] 112 | 113 | element := 0 114 | for { 115 | flag := bitIO.ReadBit(1) 116 | if flag == 0 { 117 | element = int(bitIO.ReadBit(15)) 118 | } else { 119 | element = int(bitIO.ReadBit(18)) 120 | } 121 | if bitIO.ByteOffset() > dataSize { 122 | break 123 | } 124 | var entry []byte 125 | if x, ok := dictionary[element]; ok { 126 | entry = make([]byte, len(x)) 127 | copy(entry, x) 128 | } else if element == dictionaryCount { 129 | entry = append(w, w[0]) 130 | } else { 131 | panic(fmt.Sprintf("Bad compressed element: %d", element)) 132 | } 133 | result = append(result, entry...) 134 | w = append(w, entry[0]) 135 | dictionary[dictionaryCount] = w 136 | dictionaryCount++ 137 | w = entry 138 | } 139 | return result 140 | } 141 | 142 | func compressLZW2(data []byte, size int, last string) (count int, compressed []byte, lastElement string) { 143 | count = 0 144 | dictionary := make(map[string]uint64) 145 | for i := 0; i < 256; i++ { 146 | dictionary[string(byte(i))] = uint64(i) 147 | } 148 | dictionaryCount := uint64(len(dictionary) + 1) 149 | element := "" 150 | if len(last) != 0 { 151 | element = last 152 | } 153 | bitIO := NewBitIO(make([]byte, size+2)) 154 | writeBit := func(code uint64) { 155 | if code > 0x7FFF { 156 | bitIO.WriteBit(1, 1) 157 | bitIO.WriteBit(code, 18) 158 | } else { 159 | bitIO.WriteBit(0, 1) 160 | bitIO.WriteBit(code, 15) 161 | } 162 | } 163 | 164 | for i, c := range data { 165 | _ = i 166 | entry := element + string(c) 167 | if _, isTrue := dictionary[entry]; isTrue { 168 | element = entry 169 | } else { 170 | writeBit(dictionary[element]) 171 | dictionary[entry] = dictionaryCount 172 | element = string(c) 173 | dictionaryCount++ 174 | } 175 | count++ 176 | if size > 0 && bitIO.ByteSize() >= size { 177 | break 178 | } 179 | } 180 | lastElement = element 181 | if bitIO.ByteSize() == 0 { 182 | if len(lastElement) != 0 { 183 | // 数据在上一片压缩完毕,此次压缩无数据,剩余lastElement,拆分加入 184 | for _, c := range lastElement { 185 | writeBit(dictionary[string(c)]) 186 | } 187 | } 188 | return count, bitIO.Bytes(), "" 189 | } else if bitIO.ByteSize() < size { 190 | // 数据压缩完毕,未达到size,则为最后一片,直接加入剩余数据 191 | if len(lastElement) != 0 { 192 | writeBit(dictionary[lastElement]) 193 | } 194 | return count, bitIO.Bytes(), "" 195 | } 196 | // 数据压缩完毕,达到size,剩余数据交给下一片 197 | return count, bitIO.Bytes(), lastElement 198 | } 199 | -------------------------------------------------------------------------------- /czimage/lzw_test.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | func TestLzwCompress(t *testing.T) { 12 | data, _ := os.ReadFile("../data/LB_EN/IMAGE/9.ori") 13 | n, com, _ := compressLZW(data, 0, "") 14 | fmt.Println(len(com), n) 15 | buf := bytes.NewBuffer(nil) 16 | tmp := make([]byte, 2) 17 | for _, d := range com { 18 | binary.LittleEndian.PutUint16(tmp, d) 19 | buf.Write(tmp) 20 | } 21 | os.WriteFile("../data/LB_EN/IMAGE/9.new.lzw", buf.Bytes(), 0666) 22 | //65276 386904 23 | } 24 | func TestLzwDeCompress(t *testing.T) { 25 | 26 | data, _ := os.ReadFile("../data/LB_EN/IMAGE/9.new.lzw") 27 | 28 | com := make([]uint16, len(data)/2) 29 | for j := 0; j < len(data); j += 2 { 30 | com[j/2] = binary.LittleEndian.Uint16(data[j : j+2]) 31 | } 32 | decom := decompressLZW(com, len(data)) 33 | fmt.Println(len(decom)) 34 | 35 | os.WriteFile("../data/LB_EN/IMAGE/9.new", decom, 0666) 36 | 37 | } 38 | func TestLzwCompressPart(t *testing.T) { 39 | //65000 385376 40 | //657 1528 41 | data, _ := os.ReadFile("../data/LB_EN/IMAGE/9.ori") 42 | n, com, l := compressLZW(data, 65000, "") 43 | fmt.Println(len(com), n) 44 | buf := bytes.NewBuffer(nil) 45 | tmp := make([]byte, 2) 46 | for _, d := range com { 47 | binary.LittleEndian.PutUint16(tmp, d) 48 | buf.Write(tmp) 49 | } 50 | os.WriteFile("../data/LB_EN/IMAGE/9.new.lzw", buf.Bytes(), 0666) 51 | 52 | n, com, l = compressLZW(data[n:], 65000, l) 53 | fmt.Println(len(com), n) 54 | buf = bytes.NewBuffer(nil) 55 | for _, d := range com { 56 | binary.LittleEndian.PutUint16(tmp, d) 57 | buf.Write(tmp) 58 | } 59 | os.WriteFile("../data/LB_EN/IMAGE/9.new.2.lzw", buf.Bytes(), 0666) 60 | 61 | } 62 | func TestLzwDeCompressPart(t *testing.T) { 63 | 64 | data, _ := os.ReadFile("../data/LB_EN/IMAGE/9.new.lzw") 65 | 66 | com := make([]uint16, len(data)/2) 67 | for j := 0; j < len(data); j += 2 { 68 | com[j/2] = binary.LittleEndian.Uint16(data[j : j+2]) 69 | } 70 | decom := bytes.NewBuffer(nil) 71 | decom.Write(decompressLZW(com, len(data))) 72 | fmt.Println(decom.Len()) 73 | 74 | data, _ = os.ReadFile("../data/LB_EN/IMAGE/9.new.2.lzw") 75 | 76 | com = make([]uint16, len(data)/2) 77 | for j := 0; j < len(data); j += 2 { 78 | com[j/2] = binary.LittleEndian.Uint16(data[j : j+2]) 79 | } 80 | decom.Write(decompressLZW(com, len(data))) 81 | fmt.Println(decom.Len()) 82 | os.WriteFile("../data/LB_EN/IMAGE/9.new", decom.Bytes(), 0666) 83 | 84 | } 85 | 86 | func TestCompress(t *testing.T) { 87 | data, _ := os.ReadFile("../data/LB_EN/IMAGE/2.dl") 88 | f, info := Compress(data, 0) 89 | fmt.Println(info) 90 | os.WriteFile("../data/LB_EN/IMAGE/2.dl.lzw", f, 0666) 91 | } 92 | -------------------------------------------------------------------------------- /czimage/testdata/明朝32_0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetor/LuckSystem/6e74e16ee90fbaf370a6287658fd8893d12bfb48/czimage/testdata/明朝32_0.bin -------------------------------------------------------------------------------- /czimage/testdata/明朝32_0.lzw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetor/LuckSystem/6e74e16ee90fbaf370a6287658fd8893d12bfb48/czimage/testdata/明朝32_0.lzw -------------------------------------------------------------------------------- /czimage/util.go: -------------------------------------------------------------------------------- 1 | package czimage 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "image" 7 | "image/draw" 8 | "io" 9 | 10 | "github.com/go-restruct/restruct" 11 | ) 12 | 13 | func FillImage(src image.Image, width, height int) (dst *image.NRGBA) { 14 | dst = image.NewNRGBA(image.Rect(0, 0, width, height)) 15 | draw.Draw(dst, dst.Bounds().Add(image.Pt(0, 0)), src, src.Bounds().Min, draw.Src) 16 | return dst 17 | } 18 | 19 | // GetOutputInfo 读取分块信息 20 | // 21 | // Description 读取分块信息 22 | // Param data []byte 23 | // Return outputInfo 24 | func GetOutputInfo(data []byte) (outputInfo *CzOutputInfo) { 25 | outputInfo = &CzOutputInfo{} 26 | err := restruct.Unpack(data, binary.LittleEndian, outputInfo) 27 | if err != nil { 28 | panic(err) 29 | } 30 | for _, block := range outputInfo.BlockInfo { 31 | outputInfo.TotalRawSize += int(block.RawSize) 32 | outputInfo.TotalCompressedSize += int(block.CompressedSize) 33 | } 34 | outputInfo.Offset = 4 + int(outputInfo.FileCount)*8 35 | return outputInfo 36 | } 37 | 38 | // WriteStruct 写入结构体 39 | // 40 | // Description 写入结构体 41 | // Param writer io.Writer 42 | // Param list ...interface{} 43 | // Return error 44 | func WriteStruct(writer io.Writer, list ...interface{}) error { 45 | for _, v := range list { 46 | temp, err := restruct.Pack(binary.LittleEndian, v) 47 | if err != nil { 48 | return err 49 | } 50 | writer.Write(temp) 51 | } 52 | return nil 53 | } 54 | 55 | // Decompress 解压数据 56 | // 57 | // Description 58 | // Param data []byte 压缩的数据 59 | // Param outputInfo *CzOutputInfo 分块信息 60 | // Return []byte 61 | func Decompress(data []byte, outputInfo *CzOutputInfo) []byte { 62 | offset := 0 63 | 64 | // fmt.Println("uncompress info", outputInfo) 65 | outputBuf := &bytes.Buffer{} 66 | for _, block := range outputInfo.BlockInfo { 67 | lzwBuf := make([]uint16, int(block.CompressedSize)) 68 | //offsetTemp := offset 69 | for j := 0; j < int(block.CompressedSize); j++ { 70 | lzwBuf[j] = binary.LittleEndian.Uint16(data[offset : offset+2]) 71 | offset += 2 72 | } 73 | //os.WriteFile("../data/LB_EN/IMAGE/2.ori.lzw", data[offsetTemp:offset], 0666) 74 | rawBuf := decompressLZW(lzwBuf, int(block.RawSize)) 75 | //panic("11") 76 | outputBuf.Write(rawBuf) 77 | } 78 | //os.WriteFile("../data/LB_EN/IMAGE/32.ori", outputBuf.Bytes(), 0666) 79 | return outputBuf.Bytes() 80 | 81 | } 82 | 83 | // Decompress2 解压数据 CZ2专用 84 | // 85 | // Description 86 | // Param data []byte 压缩的数据 87 | // Param outputInfo *CzOutputInfo 分块信息 88 | // Return []byte 89 | func Decompress2(data []byte, outputInfo *CzOutputInfo) []byte { 90 | offset := 0 91 | 92 | outputBuf := &bytes.Buffer{} 93 | for _, block := range outputInfo.BlockInfo { 94 | offsetTemp := offset 95 | offset += int(block.CompressedSize) 96 | //_ = os.WriteFile(fmt.Sprintf("C:\\Users\\wetor\\Desktop\\Prototype\\CZ2\\32\\%d_asm.src.lzw", i), 97 | // data[offsetTemp:offset], 0666) 98 | rawBuf := decompressLZW2(data[offsetTemp:offset], int(block.RawSize)) 99 | //_ = os.WriteFile(fmt.Sprintf("C:\\Users\\wetor\\Desktop\\Prototype\\CZ2\\32\\%d_asm.src.out", i), 100 | // rawBuf, 0666) 101 | outputBuf.Write(rawBuf) 102 | } 103 | return outputBuf.Bytes() 104 | 105 | } 106 | 107 | // Compress 压缩数据 108 | // 109 | // Description 压缩数据 110 | // Param data []byte 未压缩数据 111 | // Param size int 分块大小 112 | // Return compressed 113 | // Return outputInfo 114 | func Compress(data []byte, size int) (compressed []byte, outputInfo *CzOutputInfo) { 115 | 116 | if size == 0 { 117 | size = 0xFEFD 118 | } 119 | var partData []uint16 120 | offset := 0 121 | count := 0 122 | last := "" 123 | tmp := make([]byte, 2) 124 | outputBuf := &bytes.Buffer{} 125 | outputInfo = &CzOutputInfo{ 126 | TotalRawSize: len(data), 127 | BlockInfo: make([]CzBlockInfo, 0), 128 | } 129 | for { 130 | count, partData, last = compressLZW(data[offset:], size, last) 131 | if count == 0 { 132 | break 133 | } 134 | offset += count 135 | for _, d := range partData { 136 | binary.LittleEndian.PutUint16(tmp, d) 137 | outputBuf.Write(tmp) 138 | } 139 | 140 | outputInfo.BlockInfo = append(outputInfo.BlockInfo, CzBlockInfo{ 141 | CompressedSize: uint32(len(partData)), 142 | RawSize: uint32(count), 143 | }) 144 | outputInfo.FileCount++ 145 | } 146 | outputInfo.TotalCompressedSize = outputBuf.Len() / 2 147 | 148 | return outputBuf.Bytes(), outputInfo 149 | } 150 | 151 | // Compress2 压缩数据 CZ2专用 152 | // 153 | // Description 压缩数据 154 | // Param data []byte 未压缩数据 155 | // Param size int 分块大小 156 | // Return compressed 157 | // Return outputInfo 158 | func Compress2(data []byte, size int) (compressed []byte, outputInfo *CzOutputInfo) { 159 | 160 | if size == 0 { 161 | size = 0x87BDF 162 | } 163 | var partData []byte 164 | offset := 0 165 | count := 0 166 | last := "" 167 | outputBuf := &bytes.Buffer{} 168 | outputInfo = &CzOutputInfo{ 169 | TotalRawSize: len(data), 170 | BlockInfo: make([]CzBlockInfo, 0), 171 | } 172 | for { 173 | count, partData, last = compressLZW2(data[offset:], size, last) 174 | if count == 0 { 175 | break 176 | } 177 | offset += count 178 | outputBuf.Write(partData) 179 | 180 | outputInfo.BlockInfo = append(outputInfo.BlockInfo, CzBlockInfo{ 181 | CompressedSize: uint32(len(partData)), 182 | RawSize: uint32(count), 183 | }) 184 | outputInfo.FileCount++ 185 | } 186 | outputInfo.TotalCompressedSize = outputBuf.Len() 187 | return outputBuf.Bytes(), outputInfo 188 | } 189 | 190 | // ImageToNRGBA convert image.Image to image.NRGBA 191 | func ImageToNRGBA(im image.Image) *image.NRGBA { 192 | dst := image.NewNRGBA(im.Bounds()) 193 | draw.Draw(dst, im.Bounds(), im, im.Bounds().Min, draw.Src) 194 | return dst 195 | } 196 | -------------------------------------------------------------------------------- /data/AIR.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.air import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config(expr_charset=core.Charset_UTF8, # 表达式编码, core.expr 9 | text_charset=core.Charset_Unicode, # 文本编码, core.text 10 | default_export=True) # 未定义指令的参数是否全部导出 11 | 12 | def MESSAGE(): 13 | core.read_uint16(False) 14 | txt = core.read_len_str(core.text) # jp_len, jp_str 15 | if len(txt) > 0: 16 | core.read_len_str(core.expr) # en_len, en_str 17 | core.read_len_str(core.text) # zh_len, zh_len 18 | if core.can_read(): 19 | core.read(False) 20 | core.end() 21 | 22 | def MOVIE(): 23 | core.read_uint16(False) 24 | core.read_str(core.expr) # file_name 25 | core.read(False) 26 | core.end() 27 | 28 | def VARSTR_SET(): 29 | core.read_uint16(False) # const value 30 | core.read_str(core.expr) # filename_str 31 | core.read_uint16(True) # varstr_id 32 | txt = core.read_len_str(core.text) # jp_len, jp_str 33 | if len(txt) > 0: 34 | core.read_len_str(core.text) # en_len, en_str 35 | core.read_len_str(core.text) # zh_len, zh_len 36 | if core.can_read(): 37 | core.read(False) 38 | core.end() 39 | 40 | def DIALOG(): 41 | core.read_uint16(False) 42 | core.read_uint16(False) 43 | core.read_len_str(core.text) # jp_len, jp_str 44 | core.read(True) 45 | core.end() 46 | 47 | def LOG_BEGIN(): 48 | core.read_uint8(False) 49 | core.read_uint8(False) 50 | core.read_uint8(False) 51 | txt = core.read_len_str(core.text) # jp_len, jp_str 52 | if len(txt) > 0: 53 | core.read_len_str(core.text) # en_len, en_str 54 | core.read_len_str(core.text) # zh_len, zh_len 55 | if core.can_read(): 56 | core.read(False) 57 | core.end() 58 | 59 | def SELECT(): 60 | core.read_uint16() 61 | core.read_uint16() 62 | core.read_uint16(False) 63 | core.read_uint16(False) 64 | txt = core.read_len_str(core.text) # jp_len, jp_str 65 | if len(txt) > 0: 66 | core.read_len_str(core.text) # en_len, en_str 67 | core.read_len_str(core.text) # zh_len, zh_len 68 | if core.can_read(): 69 | core.read(False) 70 | core.end() 71 | -------------------------------------------------------------------------------- /data/AIR.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_BREAK 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | COUNTER_WAIT 53 | FFSTOP 54 | INIT 55 | STOP 56 | IMAGELOAD 57 | IMAGEUPDATE 58 | ARC 59 | MOVE 60 | MOVE_SKIP 61 | ROT 62 | PEND 63 | FADE 64 | SCALE 65 | SHAKE 66 | SHAKELIST 67 | BASE 68 | MCMOVE 69 | MCARC 70 | MCROT 71 | MCSHAKE 72 | MCFADE 73 | WAIT 74 | WAIT_BSKIP 75 | DRAW 76 | WIPE 77 | FRAMEON 78 | FRAMEOFF 79 | FW 80 | SCISSOR 81 | DELAY 82 | RASTER 83 | TONE 84 | SCALECOSSIN 85 | BMODE 86 | SIZE 87 | SPLINE 88 | DISP 89 | MASK 90 | FACE 91 | SEPIA 92 | SEPIA_COLOR 93 | CUSTOMMOVE 94 | SWAP 95 | ADDCOLOR 96 | SUBCOLOR 97 | SATURATION 98 | CONTRAST 99 | PRIORITY 100 | UVWH 101 | EVSCROLL 102 | COLORLEVEL 103 | NEGA 104 | TONECURVE 105 | SKIP_SCOPE_BEGIN 106 | SKIP_SCOPE_END 107 | QUAKE 108 | BGM 109 | BGM_WAIT_START 110 | BGM_WAIT_FADE 111 | BGM_PUSH 112 | BGM_POP 113 | BGM_ASYNC_FADE_STOP 114 | SE 115 | SE_STOP 116 | SE_WAIT 117 | SE_WAIT_COUNT 118 | SE_WAIT_FADE 119 | VOLUME 120 | MOVIE 121 | SETCGFLAG 122 | EX 123 | TROPHY 124 | SETBGMFLAG 125 | TASK 126 | PRINTF 127 | DIALOG 128 | VIB_PLAY 129 | VIB_FILE 130 | VIB_STOP 131 | CHAR_VOLUME 132 | SCENE_REPLAY_END 133 | SAVE_THUMBNAIL 134 | TEXTURE_PREFETCH 135 | CLOSE_PRELOAD_FILE 136 | MANPU 137 | SCENARIO 138 | HAIKEI_SET 139 | UNKNOWN -------------------------------------------------------------------------------- /data/CartagraHD.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.cartagrahd import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config(expr_charset=core.Charset_UTF8, # 表达式编码, core.expr 9 | text_charset=core.Charset_Unicode, # 文本编码, core.text 10 | default_export=True) # 未定义指令的参数是否全部导出 11 | 12 | def MESSAGE(): 13 | core.read_uint16(True) 14 | core.read_len_str(core.text) 15 | core.read_uint8() 16 | core.read(False) 17 | core.end() 18 | 19 | def SELECT(): 20 | core.read_uint16() 21 | core.read_uint16() 22 | core.read_uint16() 23 | core.read_uint16() 24 | core.read_len_str(core.text) 25 | core.read(True) 26 | core.end() 27 | 28 | def DIALOG(): 29 | core.read_uint16(False) 30 | core.read_uint16(False) 31 | core.read_len_str(core.text) 32 | core.read(False) 33 | core.end() 34 | 35 | def LOG_BEGIN(): 36 | core.read_uint8(False) 37 | core.read_uint8(False) 38 | core.read_uint8(False) 39 | core.read_len_str(core.text) 40 | core.read(False) 41 | core.end() -------------------------------------------------------------------------------- /data/CartagraHD.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_PAUSE 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | FFSTOP 53 | INIT 54 | STOP 55 | IMAGELOAD 56 | IMAGEUPDATE 57 | ARC 58 | MOVE 59 | MOVE_SKIP 60 | ROT 61 | PEND 62 | FADE 63 | SCALE 64 | SHAKE 65 | SHAKELIST 66 | BASE 67 | MCMOVE 68 | MCARC 69 | MCROT 70 | MCSHAKE 71 | MCFADE 72 | WAIT 73 | WAIT_BSKIP 74 | DRAW 75 | WIPE 76 | FRAMEON 77 | FRAMEOFF 78 | FW 79 | SCISSOR 80 | DELAY 81 | RASTER 82 | TONE 83 | SCALECOSSIN 84 | BMODE 85 | SIZE 86 | SPLINE 87 | DISP 88 | MASK 89 | FACE 90 | SEPIA 91 | SEPIA_COLOR 92 | CUSTOMMOVE 93 | SWAP 94 | ADDCOLOR 95 | SUBCOLOR 96 | SATURATION 97 | CONTRAST 98 | PRIORITY 99 | UVWH 100 | EVSCROLL 101 | COLORLEVEL 102 | NEGA 103 | TONECURVE 104 | SKIP_SCOPE_BEGIN 105 | SKIP_SCOPE_END 106 | QUAKE 107 | BGM 108 | BGM_WAIT_START 109 | BGM_WAIT_FADE 110 | BGM_PUSH 111 | BGM_POP 112 | SE 113 | SE_STOP 114 | SE_WAIT 115 | SE_WAIT_COUNT 116 | SE_WAIT_FADE 117 | VOLUME 118 | MOVIE 119 | SETCGFLAG 120 | EX 121 | TROPHY 122 | SETBGMFLAG 123 | TASK 124 | PRINTF 125 | DIALOG 126 | VIB_PLAY 127 | VIB_FILE 128 | VIB_STOP 129 | CHAR_VOLUME 130 | SCENE_REPLAY_END 131 | SAVE_THUMBNAIL 132 | MANPU 133 | SCENARIO 134 | UNKNOWN -------------------------------------------------------------------------------- /data/HARMONIA.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.harmonia import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config(expr_charset=core.Charset_UTF8, # 表达式编码, core.expr 9 | text_charset=core.Charset_Unicode, # 文本编码, core.text 10 | default_export=True) # 未定义指令的参数是否全部导出 11 | 12 | def MESSAGE(): 13 | core.read_uint16(True) 14 | txt = core.read_len_str(core.text) 15 | if len(txt) > 0: 16 | core.read_len_str(core.Charset_UTF8) 17 | core.read_len_str(core.text) 18 | if core.can_read(): 19 | core.read(True) 20 | core.end() 21 | 22 | def MOVIE(): 23 | core.read_len_str(core.Charset_UTF8) 24 | core.read(False) 25 | core.end() 26 | 27 | def VARSTR_SET(): 28 | core.read_uint16(False) 29 | core.read_len_str(core.text) 30 | core.end() 31 | 32 | def LOG_BEGIN(): 33 | core.read_uint8(False) 34 | core.read_uint8(False) 35 | core.read_uint8(False) 36 | txt = core.read_len_str(core.text) 37 | if len(txt) > 0: 38 | core.read_len_str(core.text) 39 | core.read_len_str(core.text) 40 | if core.can_read(): 41 | core.read(False) 42 | core.end() 43 | 44 | def SELECT(): 45 | core.read_uint16() 46 | core.read_uint16() 47 | core.read_uint16(False) 48 | core.read_uint16(False) 49 | txt = core.read_len_str(core.text) 50 | if len(txt) > 0: 51 | core.read_len_str(core.text) 52 | core.read_len_str(core.text) 53 | if core.can_read(): 54 | core.read(False) 55 | core.end() -------------------------------------------------------------------------------- /data/HARMONIA.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_BREAK 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | COUNTER_WAIT 53 | FFSTOP 54 | INIT 55 | STOP 56 | IMAGELOAD 57 | IMAGEUPDATE 58 | ARC 59 | MOVE 60 | MOVE_SKIP 61 | ROT 62 | PEND 63 | FADE 64 | SCALE 65 | SHAKE 66 | SHAKELIST 67 | BASE 68 | MCMOVE 69 | MCARC 70 | MCROT 71 | MCSHAKE 72 | MCFADE 73 | WAIT 74 | WAIT_BSKIP 75 | DRAW 76 | WIPE 77 | FRAMEON 78 | FRAMEOFF 79 | FW 80 | SCISSOR 81 | DELAY 82 | RASTER 83 | TONE 84 | SCALECOSSIN 85 | BMODE 86 | SIZE 87 | SPLINE 88 | DISP 89 | MASK 90 | FACE 91 | SEPIA 92 | SEPIA_COLOR 93 | CUSTOMMOVE 94 | SWAP 95 | ADDCOLOR 96 | SUBCOLOR 97 | SATURATION 98 | CONTRAST 99 | PRIORITY 100 | UVWH 101 | EVSCROLL 102 | COLORLEVEL 103 | NEGA 104 | TONECURVE 105 | SKIP_SCOPE_BEGIN 106 | SKIP_SCOPE_END 107 | QUAKE 108 | BGM 109 | BGM_WAIT_START 110 | BGM_WAIT_FADE 111 | BGM_PUSH 112 | BGM_POP 113 | BGM_ASYNC_FADE_STOP 114 | SE 115 | SE_STOP 116 | SE_WAIT 117 | SE_WAIT_COUNT 118 | SE_WAIT_FADE 119 | VOLUME 120 | MOVIE 121 | SETCGFLAG 122 | EX 123 | TROPHY 124 | SETBGMFLAG 125 | TASK 126 | PRINTF 127 | DIALOG 128 | VIB_PLAY 129 | VIB_FILE 130 | VIB_STOP 131 | CHAR_VOLUME 132 | SCENE_REPLAY_END 133 | SAVE_THUMBNAIL 134 | TEXTURE_PREFETCH 135 | CLOSE_PRELOAD_FILE 136 | EF_001010 137 | FULLQUAKE_ZOOM 138 | UNKNOWN -------------------------------------------------------------------------------- /data/KANON.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.kanon import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config(expr_charset=core.Charset_UTF8, # 表达式编码, core.expr 9 | text_charset=core.Charset_Unicode, # 文本编码, core.text 10 | default_export=True) # 未定义指令的参数是否全部导出 11 | 12 | def MESSAGE(): 13 | core.read_uint16(True) 14 | txt = core.read_len_str(core.text) 15 | if len(txt) > 0: 16 | core.read_len_str(core.Charset_UTF8) 17 | core.read_len_str(core.text) 18 | if core.can_read(): 19 | core.read(True) 20 | core.end() 21 | 22 | def MOVIE(): 23 | core.read_len_str(core.Charset_UTF8) 24 | core.read(False) 25 | core.end() 26 | 27 | def VARSTR_SET(): 28 | core.read_uint16(False) 29 | core.read_len_str(core.text) 30 | core.end() 31 | 32 | def LOG_BEGIN(): 33 | core.read_uint8(False) 34 | core.read_uint8(False) 35 | core.read_uint8(False) 36 | txt = core.read_len_str(core.text) 37 | if len(txt) > 0: 38 | core.read_len_str(core.text) 39 | core.read_len_str(core.text) 40 | if core.can_read(): 41 | core.read(False) 42 | core.end() 43 | 44 | def SELECT(): 45 | core.read_uint16() 46 | core.read_uint16() 47 | core.read_uint16(False) 48 | core.read_uint16(False) 49 | txt = core.read_len_str(core.text) 50 | if len(txt) > 0: 51 | core.read_len_str(core.text) 52 | core.read_len_str(core.text) 53 | if core.can_read(): 54 | core.read(False) 55 | core.end() -------------------------------------------------------------------------------- /data/KANON.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_BREAK 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | COUNTER_WAIT 53 | FFSTOP 54 | INIT 55 | STOP 56 | IMAGELOAD 57 | IMAGEUPDATE 58 | ARC 59 | MOVE 60 | MOVE_SKIP 61 | ROT 62 | PEND 63 | FADE 64 | SCALE 65 | SHAKE 66 | SHAKELIST 67 | BASE 68 | MCMOVE 69 | MCARC 70 | MCROT 71 | MCSHAKE 72 | MCFADE 73 | WAIT 74 | WAIT_BSKIP 75 | DRAW 76 | WIPE 77 | FRAMEON 78 | FRAMEOFF 79 | FW 80 | SCISSOR 81 | DELAY 82 | RASTER 83 | TONE 84 | SCALECOSSIN 85 | BMODE 86 | SIZE 87 | SPLINE 88 | DISP 89 | MASK 90 | FACE 91 | SEPIA 92 | SEPIA_COLOR 93 | CUSTOMMOVE 94 | SWAP 95 | ADDCOLOR 96 | SUBCOLOR 97 | SATURATION 98 | CONTRAST 99 | PRIORITY 100 | UVWH 101 | EVSCROLL 102 | COLORLEVEL 103 | NEGA 104 | TONECURVE 105 | SKIP_SCOPE_BEGIN 106 | SKIP_SCOPE_END 107 | QUAKE 108 | BGM 109 | BGM_WAIT_START 110 | BGM_WAIT_FADE 111 | BGM_PUSH 112 | BGM_POP 113 | BGM_ASYNC_FADE_STOP 114 | SE 115 | SE_STOP 116 | SE_WAIT 117 | SE_WAIT_COUNT 118 | SE_WAIT_FADE 119 | VOLUME 120 | MOVIE 121 | SETCGFLAG 122 | EX 123 | TROPHY 124 | SETBGMFLAG 125 | TASK 126 | PRINTF 127 | DIALOG 128 | VIB_PLAY 129 | VIB_FILE 130 | VIB_STOP 131 | CHAR_VOLUME 132 | SCENE_REPLAY_END 133 | SAVE_THUMBNAIL 134 | TEXTURE_PREFETCH 135 | CLOSE_PRELOAD_FILE 136 | MANPU 137 | SCENARIO 138 | UNKNOWN -------------------------------------------------------------------------------- /data/LB_EN/OPCODE.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | SET 14 | FLAGCLR 15 | GOTO 16 | ONGOTO 17 | GOSUB 18 | IFY 19 | IFN 20 | RETURN 21 | JUMP 22 | FARCALL 23 | FARRETURN 24 | JUMPPOINT 25 | END 26 | VARSTR_SET 27 | TALKNAME_SET 28 | ARFLAGSET 29 | COLORBG_SET 30 | SPLINE_SET 31 | SHAKELIST_SET 32 | MESSAGE 33 | MESSAGE_CLEAR 34 | SELECT 35 | CLOSE_WINDOW 36 | LOG 37 | LOG_PAUSE 38 | LOG_END 39 | VOICE 40 | WAIT_COUNT 41 | WAIT_TIME 42 | FFSTOP 43 | INIT 44 | STOP 45 | IMAGELOAD 46 | IMAGEUPADTE 47 | ARC 48 | MOVE 49 | MOVE2 50 | ROT 51 | PEND 52 | FADE 53 | SCALE 54 | SHAKE 55 | SHAKELIST 56 | BASE 57 | MCMOVE 58 | MCARC 59 | MCROT 60 | MCSHAKE 61 | MCFADE 62 | WAIT 63 | DRAW 64 | WIPE 65 | FRAMEON 66 | FRAMEOFF 67 | FW 68 | SCISSOR 69 | DELAY 70 | RASTER 71 | TONE 72 | SCALECOSSIN 73 | BMODE 74 | SIZE 75 | SPLINE 76 | DISP 77 | MASK 78 | SG_QUAKE 79 | BGM 80 | BGM_WAITSTART 81 | BGM_WAITFADE 82 | SE 83 | SE_STOP 84 | SE_WAIT 85 | VOLUME 86 | MOVIE 87 | SETCGFLAG 88 | EX 89 | TROPHY 90 | SETBGMFLAG 91 | TASK 92 | BTFUNC 93 | BATTLE 94 | KOEP 95 | BT_ACCESSORY_SELECT 96 | UNDO_CLEAR 97 | PTFUNC 98 | PT 99 | GMFUNC 100 | GM 101 | DEL_CALLSTACK 102 | FULLQUAKE_ZOOM 103 | LBFUNC 104 | LBBG 105 | HAIKEI_SET 106 | SAYAVOICETEXT 107 | UNKNOWN -------------------------------------------------------------------------------- /data/LOOPERS.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.loopers import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config(expr_charset=core.Charset_UTF8, # 表达式编码, core.expr 9 | text_charset=core.Charset_Unicode, # 文本编码, core.text 10 | default_export=True) # 未定义指令的参数是否全部导出 11 | 12 | def MESSAGE(): 13 | core.read_uint16(True) 14 | txt = core.read_len_str(core.text) 15 | if len(txt) > 0: 16 | core.read_len_str(core.Charset_UTF8) 17 | core.read_len_str(core.text) 18 | if core.can_read(): 19 | core.read(True) 20 | core.end() 21 | 22 | def VARSTR_SET(): 23 | core.read_uint16(True) 24 | core.read_len_str(core.text) 25 | core.end() 26 | -------------------------------------------------------------------------------- /data/LOOPERS.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_PAUSE 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | FFSTOP 53 | INIT 54 | STOP 55 | IMAGELOAD 56 | IMAGEUPDATE 57 | ARC 58 | MOVE 59 | MOVE_SKIP 60 | ROT 61 | PEND 62 | FADE 63 | SCALE 64 | SHAKE 65 | SHAKELIST 66 | BASE 67 | MCMOVE 68 | MCARC 69 | MCROT 70 | MCSHAKE 71 | MCFADE 72 | WAIT 73 | WAIT_BSKIP 74 | DRAW 75 | WIPE 76 | FRAMEON 77 | FRAMEOFF 78 | FW 79 | SCISSOR 80 | DELAY 81 | RASTER 82 | TONE 83 | SCALECOSSIN 84 | BMODE 85 | SIZE 86 | SPLINE 87 | DISP 88 | MASK 89 | FACE 90 | SEPIA 91 | SEPIA_COLOR 92 | CUSTOMMOVE 93 | SWAP 94 | ADDCOLOR 95 | SUBCOLOR 96 | SATURATION 97 | CONTRAST 98 | PRIORITY 99 | UVWH 100 | EVSCROLL 101 | COLORLEVEL 102 | NEGA 103 | TONECURVE 104 | SKIP_SCOPE_BEGIN 105 | SKIP_SCOPE_END 106 | QUAKE 107 | BGM 108 | BGM_WAIT_START 109 | BGM_WAIT_FADE 110 | BGM_PUSH 111 | BGM_POP 112 | SE 113 | SE_STOP 114 | SE_WAIT 115 | SE_WAIT_COUNT 116 | SE_WAIT_FADE 117 | VOLUME 118 | MOVIE 119 | SETCGFLAG 120 | EX 121 | TROPHY 122 | SETBGMFLAG 123 | TASK 124 | PRINTF 125 | DIALOG 126 | VIB_PLAY 127 | VIB_FILE 128 | VIB_STOP 129 | CHAR_VOLUME 130 | SCENE_REPLAY_END 131 | SAVE_THUMBNAIL 132 | MANPU 133 | SCENARIO 134 | SCRIPTLINE 135 | COUNTER_SET 136 | COUNTER_WAIT 137 | UNKNOWN -------------------------------------------------------------------------------- /data/LUNARiA.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.lunaria import * 3 | 4 | def Init(): 5 | core.set_config( 6 | expr_charset=core.Charset_UTF8, 7 | text_charset=core.Charset_Unicode, 8 | default_export=True 9 | ) 10 | 11 | def MESSAGE(): 12 | core.read_uint16(True) # voice_id 13 | txt = core.read_len_str(core.text) # msg_jp_len, msg_jp_str 14 | if len(txt) > 0: # for messages without text 15 | core.read_len_str(core.Charset_UTF8) # msg_en_len, msg_en_str 16 | core.read_len_str(core.text) # msg_cn_len, msg_cn_str 17 | if core.can_read(): 18 | core.read(False) 19 | core.end() 20 | 21 | def LOG_BEGIN(): 22 | core.read_uint8(False) 23 | core.read_uint8(False) 24 | core.read_uint8(False) 25 | core.read_len_str(core.text) # msg_jp_len, msg_jp_str 26 | core.read_len_str(core.text) # msg_en_len, msg_en_str 27 | core.read_len_str(core.text) # msg_cn_len, msg_cn_str 28 | core.read(False) 29 | core.end() 30 | 31 | def MOVIE(): 32 | core.read_len_str(core.Charset_UTF8) 33 | core.read(False) 34 | core.end() 35 | 36 | def VARSTR_SET(): 37 | core.read_len_str(core.Charset_UTF8) # jp_len, jp_str 38 | core.read_uint16(True) # var_id 39 | core.read_len_str(core.Charset_Unicode) # en_len, en_str 40 | core.read_len_str(core.Charset_Unicode) # en_len2, en_str2 41 | core.read_len_str(core.Charset_Unicode) # en_len3, en_str3 42 | core.end() 43 | 44 | def DIALOG(): 45 | core.read_uint16(False) 46 | core.read_uint16(False) 47 | core.read_len_str(core.text) 48 | core.read(False) 49 | core.end() -------------------------------------------------------------------------------- /data/LUNARiA.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_BREAK 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | COUNTER_WAIT 53 | FFSTOP 54 | INIT 55 | STOP 56 | IMAGELOAD 57 | IMAGEUPDATE 58 | ARC 59 | MOVE 60 | MOVE_SKIP 61 | ROT 62 | PEND 63 | FADE 64 | SCALE 65 | SHAKE 66 | SHAKELIST 67 | BASE 68 | MCMOVE 69 | MCARC 70 | MCROT 71 | MCSHAKE 72 | MCFADE 73 | WAIT 74 | WAIT_BSKIP 75 | DRAW 76 | WIPE 77 | FRAMEON 78 | FRAMEOFF 79 | FW 80 | SCISSOR 81 | DELAY 82 | RASTER 83 | TONE 84 | SCALECOSSIN 85 | BMODE 86 | SIZE 87 | SPLINE 88 | DISP 89 | MASK 90 | FACE 91 | SEPIA 92 | SEPIA_COLOR 93 | CUSTOMMOVE 94 | SWAP 95 | ADDCOLOR 96 | SUBCOLOR 97 | SATURATION 98 | CONTRAST 99 | PRIORITY 100 | UVWH 101 | EVSCROLL 102 | COLORLEVEL 103 | NEGA 104 | TONECURVE 105 | SKIP_SCOPE_BEGIN 106 | SKIP_SCOPE_END 107 | QUAKE 108 | BGM 109 | BGM_WAIT_START 110 | BGM_WAIT_FADE 111 | BGM_PUSH 112 | BGM_POP 113 | BGM_ASYNC_FADE_STOP 114 | SE 115 | SE_STOP 116 | SE_WAIT 117 | SE_WAIT_COUNT 118 | SE_WAIT_FADE 119 | VOLUME 120 | MOVIE 121 | SETCGFLAG 122 | EX 123 | TROPHY 124 | SETBGMFLAG 125 | TASK 126 | PRINTF 127 | DIALOG 128 | VIB_PLAY 129 | VIB_FILE 130 | VIB_STOP 131 | CHAR_VOLUME 132 | SCENE_REPLAY_END 133 | SAVE_THUMBNAIL 134 | TEXTURE_PREFETCH 135 | CLOSE_PRELOAD_FILE 136 | MANPU 137 | SCENARIO 138 | UNKNOWN -------------------------------------------------------------------------------- /data/PlanetarianSG.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.air import * 3 | 4 | def Init(): 5 | # core.Charset_UTF8 6 | # core.Charset_Unicode 7 | # core.Charset_SJIS 8 | core.set_config( 9 | expr_charset=core.Charset_UTF8, 10 | text_charset=core.Charset_Unicode, 11 | default_export=True 12 | ) 13 | 14 | def MOVIE(): 15 | core.read_len_str(core.Charset_UTF8) 16 | core.read(False) 17 | core.end() 18 | 19 | def MESSAGE(): 20 | core.read_uint16(True) # voice_id 21 | txt = core.read_len_str(core.text) # jp_len, jp_str 22 | if len(txt) > 0: 23 | core.read_len_str(core.expr) # en_len, en_str 24 | core.read_len_str(core.text) # fr_len, fr_str 25 | core.read_len_str(core.text) # cn1_len, cn1_str 26 | core.read_len_str(core.text) # cn2_len, cn2_str 27 | if core.can_read(): 28 | core.read(False) 29 | core.end() 30 | 31 | def VARSTR_SET(): 32 | core.read_len_str(core.expr) # filename_len, filename_str 33 | core.read_uint16(False) # var_id 34 | core.read_len_str(core.text) # jp_len, jp_str 35 | core.read_len_str(core.text) # en_len, en_str 36 | core.read_len_str(core.text) # fr_len, fr_str 37 | core.read_len_str(core.text) # cn1_len, cn1_str 38 | core.read_len_str(core.text) # cn2_len, cn2_str 39 | core.end() 40 | 41 | def LOG_BEGIN(): 42 | core.read_uint8(False) 43 | core.read_uint8(False) 44 | core.read_uint8(False) 45 | txt = core.read_len_str(core.text) # jp_len, jp_str 46 | if len(txt) > 0: 47 | core.read_len_str(core.text) # en_len, en_str 48 | core.read_len_str(core.text) # fr_len, fr_str 49 | core.read_len_str(core.text) # cn1_len, cn1_str 50 | core.read_len_str(core.text) # cn2_len, cn2_str 51 | if core.can_read(): 52 | core.read(False) 53 | core.end() 54 | 55 | 56 | def DIALOG(): 57 | core.read_uint16(False) 58 | core.read_uint16(False) 59 | core.read_len_str(core.text) # jp_len, jp_str 60 | if core.can_read(): 61 | core.read(True) 62 | core.end() -------------------------------------------------------------------------------- /data/PlanetarianSG.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | STARTUP_BETGIN 28 | STARTUP_END 29 | TASKSCRVAR 30 | VARSTR_SET 31 | VARSTR_ALLOC 32 | ARFLAGSET 33 | COLORBG_SET 34 | SPLINE_SET 35 | SHAKELIST_SET 36 | SCISSOR_TRIANGLELIST_SET 37 | MESSAGE 38 | MESSAGE_CLEAR 39 | MESSAGE_WAIT 40 | MESSAGE_AR_SET 41 | SELECT 42 | CLOSE_WINDOW 43 | FADE_WINDOW 44 | LOG_BEGIN 45 | LOG_BREAK 46 | LOG_END 47 | VOICE 48 | VOICE_STOP 49 | WAIT_COUNT 50 | WAIT_TIME 51 | WAIT_TEXTFEED 52 | COUNTER_WAIT 53 | FFSTOP 54 | INIT 55 | STOP 56 | IMAGELOAD 57 | IMAGEUPDATE 58 | ARC 59 | MOVE 60 | MOVE_SKIP 61 | ROT 62 | PEND 63 | FADE 64 | SCALE 65 | SHAKE 66 | SHAKELIST 67 | BASE 68 | MCMOVE 69 | MCARC 70 | MCROT 71 | MCSHAKE 72 | MCFADE 73 | WAIT 74 | WAIT_BSKIP 75 | DRAW 76 | WIPE 77 | FRAMEON 78 | FRAMEOFF 79 | FW 80 | SCISSOR 81 | DELAY 82 | RASTER 83 | TONE 84 | SCALECOSSIN 85 | BMODE 86 | SIZE 87 | SPLINE 88 | DISP 89 | MASK 90 | FACE 91 | SEPIA 92 | SEPIA_COLOR 93 | CUSTOMMOVE 94 | SWAP 95 | ADDCOLOR 96 | SUBCOLOR 97 | SATURATION 98 | CONTRAST 99 | PRIORITY 100 | UVWH 101 | EVSCROLL 102 | COLORLEVEL 103 | NEGA 104 | TONECURVE 105 | SKIP_SCOPE_BEGIN 106 | SKIP_SCOPE_END 107 | QUAKE 108 | BGM 109 | BGM_WAIT_START 110 | BGM_WAIT_FADE 111 | BGM_PUSH 112 | BGM_POP 113 | BGM_ASYNC_FADE_STOP 114 | SE 115 | SE_STOP 116 | SE_WAIT 117 | SE_WAIT_COUNT 118 | SE_WAIT_FADE 119 | VOLUME 120 | MOVIE 121 | SETCGFLAG 122 | EX 123 | TROPHY 124 | SETBGMFLAG 125 | TASK 126 | PRINTF 127 | DIALOG 128 | VIB_PLAY 129 | VIB_FILE 130 | VIB_STOP 131 | CHAR_VOLUME 132 | SCENE_REPLAY_END 133 | SAVE_THUMBNAIL 134 | TEXTURE_PREFETCH 135 | CLOSE_PRELOAD_FILE 136 | CLEAR_UNDO_INFO 137 | BACKJUMP_LOG_RESTORE_POINT 138 | MANPU 139 | SCENARIO 140 | UNKNOWN -------------------------------------------------------------------------------- /data/SP.py: -------------------------------------------------------------------------------- 1 | import core 2 | from base.sp import * 3 | 4 | # 在没有载入OPCODE时,进行手动映射以导出脚本 5 | # 此方式导出的脚本修改后导入将会无法使用!! 6 | opcode_dict = { 7 | '0x22': 'MESSAGE', 8 | '0x25': 'SELECT' 9 | } 10 | 11 | def Init(): 12 | # core.Charset_UTF8 13 | # core.Charset_Unicode 14 | # core.Charset_SJIS 15 | core.set_config(expr_charset=core.Charset_SJIS, # 表达式编码, core.expr 16 | text_charset=core.Charset_Unicode, # 文本编码, core.text 17 | default_export=True) # 未定义指令的参数是否全部导出 18 | 19 | def MESSAGE(): 20 | core.read_uint16(True) 21 | core.read_len_str(core.text) 22 | core.read_uint8() 23 | core.end() 24 | 25 | def SELECT(): 26 | core.read_uint16() 27 | core.read_uint16() 28 | core.read_uint16(False) 29 | core.read_uint16(False) 30 | core.read_len_str(core.text) 31 | core.read(False) 32 | core.end() 33 | -------------------------------------------------------------------------------- /data/SP/OPCODE.txt: -------------------------------------------------------------------------------- 1 | EQU 2 | EQUN 3 | EQUV 4 | ADD 5 | SUB 6 | MUL 7 | DIV 8 | MOD 9 | AND 10 | OR 11 | RANDOM 12 | VARSTR 13 | VARSTR_ADD 14 | SET 15 | FLAGCLR 16 | GOTO 17 | ONGOTO 18 | GOSUB 19 | IFY 20 | IFN 21 | RETURN 22 | JUMP 23 | FARCALL 24 | FARRETURN 25 | JUMPPOINT 26 | END 27 | VARSTR_SET 28 | VARSTR_ALLOC 29 | TALKNAME_SET 30 | ARFLAGSET 31 | COLORBG_SET 32 | SPLINE_SET 33 | SHAKELIST_SET 34 | SCISSOR_TRIANGLELIST_SET 35 | MESSAGE 36 | MESSAGE_CLEAR 37 | MESSAGE_WAIT 38 | SELECT 39 | CLOSE_WINDOW 40 | LOG 41 | LOG_PAUSE 42 | LOG_END 43 | VOICE 44 | WAIT_COUNT 45 | WAIT_TIME 46 | WAIT_TEXTFEED 47 | FFSTOP 48 | INIT 49 | STOP 50 | IMAGELOAD 51 | IMAGEUPADTE 52 | ARC 53 | MOVE 54 | MOVE2 55 | ROT 56 | PEND 57 | FADE 58 | SCALE 59 | SHAKE 60 | SHAKELIST 61 | BASE 62 | MCMOVE 63 | MCARC 64 | MCROT 65 | MCSHAKE 66 | MCFADE 67 | WAIT 68 | DRAW 69 | WIPE 70 | FRAMEON 71 | FRAMEOFF 72 | FW 73 | SCISSOR 74 | DELAY 75 | RASTER 76 | TONE 77 | SCALECOSSIN 78 | BMODE 79 | SIZE 80 | SPLINE 81 | DISP 82 | MASK 83 | FACE 84 | SEPIA 85 | SEPIA_COLOR 86 | CUSTOMMOVE 87 | SWAP 88 | ADDCOLOR 89 | SUBCOLOR 90 | SATURATION 91 | PRIORITY 92 | UVWH 93 | EVSCROLL 94 | COLORLEVEL 95 | QUAKE 96 | BGM 97 | BGM_WAITSTART 98 | BGM_WAITFADE 99 | BGM_PUSH 100 | BGM_POP 101 | SE 102 | SE_STOP 103 | SE_WAIT 104 | SE_WAIT_COUNT 105 | VOLUME 106 | MOVIE 107 | SETCGFLAG 108 | EX 109 | TROPHY 110 | SETBGMFLAG 111 | TASK 112 | PRINTF 113 | WAIT_FADE 114 | MYSCALE 115 | MYSCALE_CLEAR 116 | ENROLL_WAIT 117 | ENROLL_BGSTART 118 | ENROLL_FRAMEENABLE 119 | DATEEYECATCH 120 | MAPSELECT 121 | UNKNOWN -------------------------------------------------------------------------------- /data/base/air.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (expr_str, {jump}) 5 | core.read_len_str(core.expr) 6 | core.read_jump() 7 | core.end() 8 | 9 | def IFY(): 10 | # IFY (expr_str, {jump}) 11 | core.read_len_str(core.expr) 12 | core.read_jump() 13 | core.end() 14 | 15 | def FARCALL(): 16 | # FARCALL (index, file_str, {jump}) 17 | core.read_uint16(True) 18 | file = core.read_len_str(core.expr) 19 | core.read_jump(file) 20 | core.end() 21 | 22 | def GOTO(): 23 | # GOTO ({jump}) 24 | core.read_jump() 25 | core.end() 26 | 27 | def GOSUB(): 28 | # GOTO (int, {jump}) 29 | core.read_uint16(True) 30 | core.read_jump() 31 | core.end() 32 | 33 | def JUMP(): 34 | # JUMP (file_str, {jump}) 35 | file = core.read_len_str(core.expr) 36 | if core.can_read(): 37 | core.read_jump(file) 38 | core.end() 39 | -------------------------------------------------------------------------------- /data/base/cartagrahd.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (int, expr_str, {jump}) 5 | core.read_uint16(True) 6 | core.read_str(core.expr) 7 | core.read_jump() 8 | core.end() 9 | 10 | def IFY(): 11 | # IFY (int, expr_str, {jump}) 12 | core.read_uint16(True) 13 | core.read_str(core.expr) 14 | core.read_jump() 15 | core.end() 16 | 17 | def GOTO(): 18 | # GOTO ({jump}) 19 | core.read_jump() 20 | core.end() 21 | 22 | def JUMP(): 23 | # JUMP (int, file_str, {jump}) 24 | core.read_uint16(True) 25 | file = core.read_str(core.expr) 26 | if core.can_read(): 27 | core.read_jump(file) 28 | core.end() 29 | -------------------------------------------------------------------------------- /data/base/harmonia.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (expr_str, {jump}) 5 | core.read_len_str(core.expr) 6 | core.read_jump() 7 | core.end() 8 | 9 | def IFY(): 10 | # IFY (expr_str, {jump}) 11 | core.read_len_str(core.expr) 12 | core.read_jump() 13 | core.end() 14 | 15 | def FARCALL(): 16 | # FARCALL (index, file_str, {jump}) 17 | core.read_uint16(True) 18 | file = core.read_len_str(core.expr) 19 | core.read_jump(file) 20 | core.end() 21 | 22 | def GOTO(): 23 | # GOTO ({jump}) 24 | core.read_jump() 25 | core.end() 26 | 27 | def GOSUB(): 28 | # GOTO (int, {jump}) 29 | core.read_uint16(True) 30 | core.read_jump() 31 | core.end() 32 | 33 | def JUMP(): 34 | # JUMP (file_str, {jump}) 35 | file = core.read_len_str(core.expr) 36 | if core.can_read(): 37 | core.read_jump(file) 38 | core.end() 39 | -------------------------------------------------------------------------------- /data/base/kanon.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (expr_str, {jump}) 5 | core.read_len_str(core.expr) 6 | core.read_jump() 7 | core.end() 8 | 9 | def IFY(): 10 | # IFY (expr_str, {jump}) 11 | core.read_len_str(core.expr) 12 | core.read_jump() 13 | core.end() 14 | 15 | def FARCALL(): 16 | # FARCALL (index, file_str, {jump}) 17 | core.read_uint16(True) 18 | file = core.read_len_str(core.expr) 19 | core.read_jump(file) 20 | core.end() 21 | 22 | def GOTO(): 23 | # GOTO ({jump}) 24 | core.read_jump() 25 | core.end() 26 | 27 | def GOSUB(): 28 | # GOTO (int, {jump}) 29 | core.read_uint16(True) 30 | core.read_jump() 31 | core.end() 32 | 33 | def JUMP(): 34 | # JUMP (file_str, {jump}) 35 | file = core.read_len_str(core.expr) 36 | if core.can_read(): 37 | core.read_jump(file) 38 | core.end() 39 | -------------------------------------------------------------------------------- /data/base/loopers.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (expr_str, {jump}) 5 | core.read_len_str(core.expr) 6 | core.read_jump() 7 | core.end() 8 | 9 | def IFY(): 10 | # IFY (expr_str, {jump}) 11 | core.read_len_str(core.expr) 12 | core.read_jump() 13 | core.end() 14 | 15 | def FARCALL(): 16 | # FARCALL (index, file_str, {jump}) 17 | core.read_uint16(True) 18 | file = core.read_len_str(core.expr) 19 | core.read_jump(file) 20 | core.end() 21 | 22 | def GOTO(): 23 | # GOTO ({jump}) 24 | core.read_jump() 25 | core.end() 26 | 27 | def GOSUB(): 28 | # GOTO (int, {jump}) 29 | core.read_uint16(True) 30 | core.read_jump() 31 | core.end() 32 | 33 | def JUMP(): 34 | # JUMP (file_str, {jump}) 35 | file = core.read_len_str(core.expr) 36 | if core.can_read(): 37 | core.read_jump(file) 38 | core.end() 39 | -------------------------------------------------------------------------------- /data/base/lunaria.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def GOTO(): 4 | # GOTO ({jump}) 5 | core.read_jump() 6 | core.end() 7 | 8 | def FARCALL(): 9 | # FARCALL (index, file_str, {jump}) 10 | core.read_uint16(True) 11 | file = core.read_len_str(core.expr) 12 | core.read_jump(file) 13 | core.end() 14 | 15 | def IFN(): 16 | # IFN (expr_len, expr_str, {jump}) 17 | core.read_len_str(core.expr) 18 | core.read_jump() 19 | core.end() -------------------------------------------------------------------------------- /data/base/sp.py: -------------------------------------------------------------------------------- 1 | import core 2 | 3 | def IFN(): 4 | # IFN (expr_str, {jump}) 5 | core.read_str(core.expr) 6 | core.read_jump() 7 | core.end() 8 | 9 | def IFY(): 10 | # IFY (expr_str, {jump}) 11 | core.read_str(core.expr) 12 | core.read_jump() 13 | core.end() 14 | 15 | def FARCALL(): 16 | # FARCALL (index, file_str, {jump}) 17 | core.read_uint16(True) 18 | file = core.read_str(core.expr) 19 | core.read_jump(file) 20 | core.end() 21 | 22 | def GOTO(): 23 | # GOTO ({jump}) 24 | core.read_jump() 25 | core.end() 26 | 27 | def GOSUB(): 28 | # GOTO (int, {jump}) 29 | core.read_uint16(True) 30 | core.read_jump() 31 | core.end() 32 | 33 | def JUMP(): 34 | # JUMP (file_str, {jump}) 35 | file = core.read_str(core.expr) 36 | if core.can_read(): 37 | core.read_jump(file) 38 | core.end() 39 | -------------------------------------------------------------------------------- /font/font_test.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/glog" 6 | "golang.org/x/image/font/opentype" 7 | "golang.org/x/image/math/fixed" 8 | "image" 9 | "image/draw" 10 | "image/png" 11 | "log" 12 | "lucksystem/charset" 13 | "lucksystem/pak" 14 | "os" 15 | "testing" 16 | 17 | "github.com/go-restruct/restruct" 18 | ) 19 | 20 | func TestFont(t *testing.T) { 21 | 22 | restruct.EnableExprBeta() 23 | pak := pak.LoadPak( 24 | "../data/LB_EN/FONT.PAK", 25 | charset.UTF_8, 26 | ) 27 | fmt.Println("pak header", pak.Header) 28 | 29 | font := LoadLucaFontPak(pak, "モダン", 32) 30 | img := font.GetStringImage("ЁАБ #12ABjgkloa!.理樹@「…は? こんな夜に? どこで?」") 31 | f, _ := os.Create("../data/LB_EN/IMAGE/str/str.png") 32 | png.Encode(f, img) 33 | f.Close() 34 | // imgs := font.GetStringImageList("真人@「…戦いさ」") 35 | 36 | // for i, img := range imgs { 37 | // f, _ := os.Create("../data/LB_EN/IMAGE/str/" + strconv.Itoa(i) + ".png") 38 | // png.Encode(f, img) 39 | // f.Close() 40 | // } 41 | 42 | } 43 | func TestFreeTypeFont(t *testing.T) { 44 | data, err := os.ReadFile("../data/Other/Font/ARHei-400.ttf") 45 | if err != nil { 46 | log.Println(err) 47 | return 48 | } 49 | font, err := opentype.Parse(data) 50 | if err != nil { 51 | log.Println(err) 52 | return 53 | } 54 | fmt.Println(font.NumGlyphs()) 55 | pic := image.NewNRGBA(image.Rect(0, 0, 800, 600)) 56 | 57 | fontFace, err := opentype.NewFace(font, &opentype.FaceOptions{ 58 | Size: 24, 59 | DPI: 72, 60 | }) 61 | if err != nil { 62 | glog.Fatalln(err) 63 | } 64 | 65 | //c.Text(100, 100, "测试汉字,#12ABjgkloa!.理樹@「…は? こんな夜に? どこで?」") 66 | 67 | xx1, xx2, _ := fontFace.GlyphBounds('б') 68 | fmt.Println(xx1.Max.X.Ceil(), xx1.Max.Y.Ceil(), xx2.Ceil()) 69 | _, img, _, width, ok := fontFace.Glyph(fixed.Point26_6{X: 0, Y: 0}, 'б') 70 | draw.Draw(pic, pic.Bounds().Add(image.Pt(10, 10)), img, img.Bounds().Min, draw.Src) 71 | draw.Draw(pic, pic.Bounds().Add(image.Pt(34, 10)), img, img.Bounds().Min, draw.Src) 72 | draw.Draw(pic, pic.Bounds().Add(image.Pt(58, 10)), img, img.Bounds().Min, draw.Src) 73 | _, img, _, width, ok = fontFace.Glyph(fixed.Point26_6{X: 0, Y: 0}, '测') 74 | draw.Draw(pic, pic.Bounds().Add(image.Pt(10, 10)), img, img.Bounds().Min, draw.Src) 75 | fmt.Println(width.Ceil(), ok) 76 | f, _ := os.Create("../data/Other/Font/ARHei-400.ttf.png") 77 | defer f.Close() 78 | png.Encode(f, pic) 79 | 80 | } 81 | 82 | func TestCreateLucaFont(t *testing.T) { 83 | restruct.EnableExprBeta() 84 | font, _ := os.Open("../data/Other/Font/ARHei-400.ttf") 85 | defer font.Close() 86 | f := CreateLucaFont(24, font, " !@#$%.,abcdefgABCDEFG12345测试中文汉字") 87 | pngFile, _ := os.Create("../data/Other/Font/ARHei-400.ttf.png") 88 | f.Export(pngFile, "../data/Other/Font/ARHei-400.allChar.txt") 89 | 90 | czFile, _ := os.Create("../data/Other/Font/ARHei-400.ttf.cz") 91 | infoFile, _ := os.Create("../data/Other/Font/ARHei-400.ttf.info") 92 | f.Write(czFile, infoFile) 93 | } 94 | 95 | func TestEidtLucaFont(t *testing.T) { 96 | restruct.EnableExprBeta() 97 | pak := pak.LoadPak( 98 | "../data/LB_EN/FONT.PAK", 99 | charset.UTF_8, 100 | ) 101 | 102 | fmt.Println("pak header", pak.Header) 103 | 104 | f := LoadLucaFontPak(pak, "モダン", 32) 105 | font, _ := os.Open("../data/Other/Font/ARHei-400.ttf") 106 | defer font.Close() 107 | f.ReplaceChars(font, " !@#$%.,abcdefgABCDEFG12345测试中文汉字", 7113, false) 108 | //f := CreateLucaFont("测试字体", 24, "../data/Other/Font/ARHei-400.ttf", " !@#$%.,abcdefgABCDEFG12345测试中文汉字") 109 | 110 | pngFile, _ := os.Create("../data/Other/Font/モダン32e.png") 111 | f.Export(pngFile, "../data/Other/Font/モダン32e.allChar.txt") 112 | 113 | czFile, _ := os.Create("../data/Other/Font/モダン32e.cz") 114 | infoFile, _ := os.Create("../data/Other/Font/モダン32e.info") 115 | f.Write(czFile, infoFile) 116 | } 117 | func TestSPFont(t *testing.T) { 118 | 119 | restruct.EnableExprBeta() 120 | 121 | pak := pak.LoadPak( 122 | "/Volumes/NTFS/WorkSpace/Github/SummerPockets/font/FONT.PAK", 123 | charset.UTF_8, 124 | ) 125 | 126 | fmt.Println("pak header", pak.Header) 127 | 128 | for i, f := range pak.Files { 129 | if i < 160 { 130 | continue 131 | } 132 | fmt.Println(f.ID, f.Name, f.Offset, f.Length, f.Replace) 133 | } 134 | 135 | f := LoadLucaFontPak(pak, "モダン", 32) 136 | file, _ := os.Create("../data/Other/Font/モダン32.png") 137 | defer file.Close() 138 | f.Export(file, "") 139 | 140 | } 141 | func TestLucaFont_Export(t *testing.T) { 142 | restruct.EnableExprBeta() 143 | var err error 144 | savePath := "../data/LB_EN/FONT/" 145 | infoFile := "info32" 146 | czFile := "明朝32" 147 | txtName := "info32.txt" 148 | pngName := "明朝32.png" 149 | 150 | infoData, err := os.ReadFile(savePath + infoFile) 151 | if err != nil { 152 | panic(err) 153 | } 154 | czData, err := os.ReadFile(savePath + czFile) 155 | if err != nil { 156 | panic(err) 157 | } 158 | font := LoadLucaFont(infoData, czData) 159 | 160 | txtFile := savePath + txtName 161 | 162 | pngFile, _ := os.Create(savePath + pngName) 163 | defer pngFile.Close() 164 | 165 | err = font.Export(pngFile, txtFile) 166 | if err != nil { 167 | panic(err) 168 | } 169 | } 170 | 171 | func TestLucaFont_Import(t *testing.T) { 172 | restruct.EnableExprBeta() 173 | var err error 174 | savePath := "../data/LB_EN/FONT/" 175 | infoFile := "info32" 176 | czFile := "明朝32" 177 | ttfFile := "../data/Other/Font/ARHei-400.ttf" 178 | addChars := "../data/Other/Font/allchar.txt" 179 | 180 | infoData, err := os.ReadFile(savePath + infoFile) 181 | if err != nil { 182 | panic(err) 183 | } 184 | czData, err := os.ReadFile(savePath + czFile) 185 | if err != nil { 186 | panic(err) 187 | } 188 | font := LoadLucaFont(infoData, czData) 189 | ttf, _ := os.Open(ttfFile) 190 | defer ttf.Close() 191 | 192 | //=============== 193 | err = font.Import(ttf, 0, true, "") 194 | if err != nil { 195 | panic(err) 196 | } 197 | cz1, _ := os.Create(savePath + czFile + "_onlyRedraw") 198 | defer cz1.Close() 199 | info1, _ := os.Create(savePath + infoFile + "_onlyRedraw") 200 | defer info1.Close() 201 | err = font.Write(cz1, info1) 202 | if err != nil { 203 | panic(err) 204 | } 205 | //================ 206 | err = font.Import(ttf, -1, false, addChars) 207 | if err != nil { 208 | panic(err) 209 | } 210 | cz2, _ := os.Create(savePath + czFile + "_addChar") 211 | defer cz2.Close() 212 | info2, _ := os.Create(savePath + infoFile + "_addChar") 213 | defer info2.Close() 214 | err = font.Write(cz2, info2) 215 | if err != nil { 216 | panic(err) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /font/info.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/go-restruct/restruct" 6 | "github.com/golang/glog" 7 | "golang.org/x/image/font" 8 | "golang.org/x/image/font/opentype" 9 | "io" 10 | "os" 11 | ) 12 | 13 | type DrawSize struct { 14 | X uint8 // x offset 15 | W uint8 // width 16 | Y uint8 // y offset 17 | } 18 | type CharSize struct { 19 | X uint8 20 | W uint8 21 | } 22 | 23 | type Info struct { 24 | FontSize uint16 // 字体大小 25 | BlockSize uint16 // 字体所在区域大小(超过会被切割) 26 | CharNum uint16 // 字符数量 27 | CharNum2 uint16 `struct:"if=CharNum==100"` 28 | DrawSize []DrawSize `struct:"size=CharNum==100?CharNum2:CharNum"` 29 | UnicodeIndex []uint16 `struct:"size=65536"` // unicode -> imgindex 30 | UnicodeSize []CharSize `struct:"size=65536"` 31 | // FontMap map[rune]uint16 `struct:"-"` // unicode -> imgindex 32 | FontFace font.Face `struct:"-"` 33 | IndexUnicode []rune `struct:"-"` // imgindex -> unicode 34 | } 35 | 36 | func LoadFontInfoFile(file string) *Info { 37 | data, err := os.ReadFile(file) 38 | if err != nil { 39 | glog.Fatalln(err) 40 | } 41 | return LoadFontInfo(data) 42 | } 43 | func LoadFontInfo(data []byte) *Info { 44 | info := new(Info) 45 | err := restruct.Unpack(data, binary.LittleEndian, info) 46 | if err != nil { 47 | glog.Fatalln("restruct.Unpack", err) 48 | } 49 | if info.CharNum == 100 { 50 | info.CharNum = info.CharNum2 51 | info.CharNum2 = 100 52 | } 53 | // info.FontMap = make(map[rune]uint16) 54 | info.IndexUnicode = make([]rune, info.CharNum) 55 | // 6 + 3*7112 56 | // fmt.Println(info.FontSize, info.CharSize, info.CharNum) 57 | for unicode, index := range info.UnicodeIndex { 58 | if index != 0 || unicode == 32 { 59 | // info.FontMap[rune(unicode)] = index 60 | info.IndexUnicode[int(index)] = rune(unicode) 61 | } 62 | } 63 | //glog.V(6).Infoln("font info", info.FontSize, info.CharNum, info.BlockSize, len(info.FontMap)) 64 | // 65 | //for i, v := range info.UnicodeSize { 66 | // if v.W != 0 { 67 | // glog.V(6).Infof("%d %v UnicodeSize:%v", i, string(rune(i)), v) 68 | // } 69 | //} 70 | 71 | return info 72 | // for unicode, index := range info.FontMap { 73 | 74 | // uni := make([]byte, 2) 75 | // binary.LittleEndian.PutUint16(uni, uint16(unicode)) 76 | // str, _ := charset.ToUTF8(charset.Unicode, uni) 77 | 78 | // fmt.Println(index, unicode, str, info.DrawSize[index], info.UnicodeSize[unicode]) 79 | 80 | // } 81 | } 82 | 83 | func (i *Info) Get(unicode rune) (int, DrawSize, CharSize) { 84 | index := i.UnicodeIndex[unicode] 85 | if unicode != 32 && index == 0 { 86 | panic("不存在此字符 " + string(unicode)) 87 | } 88 | return int(index), i.DrawSize[index], i.UnicodeSize[unicode] 89 | } 90 | 91 | // CreateFontInfo 创建字体Info信息 92 | // Description 93 | // Param fontSize int 字体实际大小 94 | // Param blockSize int 字体所在区域大小(超过会被切割) 95 | // Return *FontInfo 96 | // 97 | func CreateFontInfo(fontSize, blockSize int) *Info { 98 | 99 | info := &Info{ 100 | FontSize: uint16(fontSize), 101 | BlockSize: uint16(blockSize), 102 | UnicodeIndex: make([]uint16, 65536), 103 | UnicodeSize: make([]CharSize, 65536), 104 | } 105 | // info.FontMap = make(map[rune]uint16) 106 | // info.IndexUnicode = make(map[int]rune) 107 | return info 108 | } 109 | 110 | // SetChars 111 | // Description 如果startIndex=0且allChar为空,则为仅重绘 112 | // Receiver i *Info 113 | // Param fontFile io.Reader 字体文件 114 | // Param allChar string 全字符串,若第一个字符不是空格,会自动补充为空格 115 | // Param startIndex int 开始位置,即字库上面跳过多少字符 116 | // Param reDraw bool 是否用新字体重绘startIndex之前的字符 117 | // 118 | func (i *Info) SetChars(fontFile io.Reader, allChar string, startIndex int, reDraw bool) { 119 | 120 | if len(allChar) == 0 && startIndex == 0 && !reDraw { 121 | // 什么都不做 122 | return 123 | } 124 | // 加载字体 125 | var err error 126 | var font *opentype.Font 127 | fontFileReaderAt, ok := fontFile.(io.ReaderAt) 128 | if ok { 129 | font, err = opentype.ParseReaderAt(fontFileReaderAt) 130 | if err != nil { 131 | glog.Fatalln(err) 132 | } 133 | } else { 134 | var data []byte 135 | data, err = io.ReadAll(fontFile) 136 | if err != nil { 137 | glog.Fatalln(err) 138 | } 139 | font, err = opentype.Parse(data) 140 | if err != nil { 141 | glog.Fatalln(err) 142 | } 143 | } 144 | 145 | i.FontFace, err = opentype.NewFace(font, &opentype.FaceOptions{ 146 | Size: float64(i.FontSize), 147 | DPI: 72, 148 | }) 149 | if err != nil { 150 | glog.Fatalln(err) 151 | } 152 | 153 | // 处理字符 154 | // 去重、排序 155 | chars := []rune(allChar) 156 | 157 | noReDraw := false 158 | if len(chars) == 0 && startIndex == 0 { 159 | if i.CharNum == 0 { 160 | glog.Fatalln("需要载入字体") 161 | } 162 | if reDraw { 163 | chars = make([]rune, 0, i.CharNum) 164 | for _, char := range i.IndexUnicode { 165 | if char == 0 { 166 | chars = append(chars, '□') 167 | } else { 168 | chars = append(chars, char) 169 | } 170 | } 171 | } else { 172 | noReDraw = true 173 | } 174 | } else { 175 | for startIndex > int(i.CharNum) || startIndex+len(chars) > int(i.CharNum) { 176 | i.DrawSize = append(i.DrawSize, DrawSize{}) 177 | i.IndexUnicode = append(i.IndexUnicode, rune(0)) 178 | i.CharNum++ 179 | } 180 | 181 | //tempDrawSize := make([]DrawSize, len(chars)) 182 | //i.DrawSize = append(i.DrawSize[:startIndex], append(tempDrawSize)...) 183 | // 184 | //tempIndexUnicode := make([]rune, len(chars)) 185 | //i.IndexUnicode = append(i.IndexUnicode[:startIndex], append(tempIndexUnicode)...) 186 | // 187 | //i.CharNum = uint16(len(i.DrawSize)) 188 | } 189 | if !noReDraw { 190 | for index := 0; index < int(i.CharNum); index++ { 191 | var char rune 192 | if index < startIndex || index >= startIndex+len(chars) { 193 | if reDraw { 194 | char = i.IndexUnicode[index] 195 | } else { 196 | continue 197 | } 198 | } else { 199 | char = chars[index-startIndex] 200 | } 201 | // i.FontMap[char] = uint16(index) 202 | i.UnicodeIndex[i.IndexUnicode[index]] = 0 // 清除原字符 203 | i.UnicodeIndex[char] = uint16(index) 204 | i.IndexUnicode[index] = char 205 | bounds, advance, ok := i.FontFace.GlyphBounds(char) 206 | 207 | if !ok { 208 | glog.Fatalf("字体文件中不存在的字符 %v %v\n", string(char), index) 209 | panic("字体文件中不存在的字符") 210 | } 211 | 212 | // fmt.Println(string(char), " ", bounds.Min.X.Floor(), " ", bounds.Min.Y.Floor()+int(i.FontSize)) 213 | w := uint8(advance.Ceil()) 214 | if char == 32 || w == 0 { 215 | w = uint8(i.FontSize) 216 | } 217 | i.DrawSize[index].X = uint8(bounds.Min.X.Floor()) 218 | i.DrawSize[index].W = w 219 | i.DrawSize[index].Y = uint8(bounds.Min.Y.Floor()) 220 | i.UnicodeSize[char].W = w 221 | 222 | } 223 | } 224 | 225 | } 226 | 227 | // Import 228 | // Description 若startIndex=0, redraw=true, allChar="", 则仅使用字体重绘原字符集 229 | // Receiver i *Info 230 | // Param r io.Reader 字体文件 231 | // Param startIndex int 开始位置。前面跳过字符数量 232 | // Param redraw bool 是否用新字体重绘startIndex之前的字符 233 | // Param allChar string 增加的全字符,若startIndex==0,且第一个字符不是空格,会自动补充为空格 234 | // Return error 235 | // 236 | func (i *Info) Import(r io.Reader, startIndex int, redraw bool, allChar string) error { 237 | i.SetChars(r, allChar, startIndex, redraw) 238 | return nil 239 | } 240 | 241 | // Export 242 | // Description 243 | // Receiver i *Info 244 | // Param w io.Writer 245 | // Return error 246 | // 247 | func (i *Info) Export(w io.Writer) error { 248 | 249 | var err error 250 | for _, char := range i.IndexUnicode { 251 | 252 | if char == 0 { 253 | _, err = w.Write([]byte(string('□'))) 254 | } else { 255 | _, err = w.Write([]byte(string(char))) 256 | } 257 | if err != nil { 258 | return err 259 | } 260 | } 261 | return nil 262 | } 263 | 264 | // Write 265 | // Description 266 | // Receiver i *Info 267 | // Param w io.Writer 268 | // Return error 269 | // 270 | func (i *Info) Write(w io.Writer) error { 271 | 272 | data, err := restruct.Pack(binary.LittleEndian, i) 273 | if err != nil { 274 | glog.Fatalln("restruct.Pack", err) 275 | } 276 | _, err = w.Write(data) 277 | 278 | return err 279 | 280 | } 281 | -------------------------------------------------------------------------------- /font/info_test.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/go-restruct/restruct" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | flag.Set("alsologtostderr", "true") 14 | flag.Set("log_dir", "log") 15 | flag.Set("v", "10") 16 | flag.Parse() 17 | 18 | ret := m.Run() 19 | os.Exit(ret) 20 | } 21 | func TestInfo(t *testing.T) { 22 | restruct.EnableExprBeta() 23 | list := []string{"info32"} 24 | for _, name := range list { 25 | file := "../data/LB_EN/IMAGE/" + name 26 | data, _ := os.ReadFile(file) 27 | info := LoadFontInfo(data) 28 | txtFile, _ := os.Create(file + ".txt") 29 | info.Export(txtFile) 30 | fmt.Println(info.CharNum, " ", len(info.IndexUnicode)) 31 | infoFile, _ := os.Create(file + ".out") 32 | info.Write(infoFile) 33 | 34 | infoFile.Close() 35 | txtFile.Close() 36 | } 37 | } 38 | func TestInfo2(t *testing.T) { 39 | restruct.EnableExprBeta() 40 | file := "../data/Other/Font/info32e.info" 41 | data, _ := os.ReadFile(file) 42 | info := LoadFontInfo(data) 43 | txtFile, _ := os.Create(file + ".txt") 44 | info.Export(txtFile) 45 | fmt.Println(info.CharNum, " ", len(info.IndexUnicode)) 46 | infoFile, _ := os.Create(file + ".out") 47 | info.Write(infoFile) 48 | 49 | } 50 | func TestStr(t *testing.T) { 51 | aaa := []int{1, 2, 3} 52 | index := 3 53 | bbb := make([]int, 10) 54 | copy(bbb, aaa[:index]) 55 | 56 | fmt.Println(bbb) 57 | } 58 | func TestInfo_Export(t *testing.T) { 59 | restruct.EnableExprBeta() 60 | var err error 61 | savePath := "../data/LB_EN/FONT/" 62 | infoFiles := []string{"info32", "info24"} 63 | 64 | //============ 65 | for _, name := range infoFiles { 66 | data, _ := os.ReadFile(savePath + name) 67 | info := LoadFontInfo(data) 68 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 69 | fs, _ := os.Create(savePath + name + "_export.txt") 70 | err = info.Export(fs) 71 | if err != nil { 72 | panic(err) 73 | } 74 | err = fs.Close() 75 | if err != nil { 76 | panic(err) 77 | } 78 | } 79 | 80 | } 81 | 82 | func TestInfo_Import(t *testing.T) { 83 | restruct.EnableExprBeta() 84 | var err error 85 | loadPath := "../data/LB_EN/FONT/" 86 | infoFiles := []string{"info32", "info24"} 87 | addChars := "!@#$%.,abcdefgAB CDEFG12345测试中文汉字" 88 | font, _ := os.Open("../data/Other/Font/ARHei-400.ttf") 89 | defer font.Close() 90 | //============ 91 | fmt.Println() 92 | for _, name := range infoFiles { 93 | data, _ := os.ReadFile(loadPath + name) 94 | info := LoadFontInfo(data) 95 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 96 | 97 | err = info.Import(font, 0, true, "") 98 | if err != nil { 99 | panic(err) 100 | } 101 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 102 | fs, _ := os.Create(loadPath + name + "_byOnlyRedraw") 103 | err = info.Write(fs) 104 | if err != nil { 105 | panic(err) 106 | } 107 | err = fs.Close() 108 | if err != nil { 109 | panic(err) 110 | } 111 | } 112 | 113 | //============ 114 | fmt.Println() 115 | for _, name := range infoFiles { 116 | data, _ := os.ReadFile(loadPath + name) 117 | info := LoadFontInfo(data) 118 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 119 | 120 | err = info.Import(font, int(info.CharNum), false, addChars) 121 | if err != nil { 122 | panic(err) 123 | } 124 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 125 | fs, _ := os.Create(loadPath + name + "_byAddChar") 126 | err = info.Write(fs) 127 | if err != nil { 128 | panic(err) 129 | } 130 | err = fs.Close() 131 | if err != nil { 132 | panic(err) 133 | } 134 | } 135 | 136 | //============ 137 | fmt.Println() 138 | for _, name := range infoFiles { 139 | data, _ := os.ReadFile(loadPath + name) 140 | info := LoadFontInfo(data) 141 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 142 | 143 | err = info.Import(font, int(info.CharNum), true, addChars) 144 | if err != nil { 145 | panic(err) 146 | } 147 | fmt.Println(name, info.CharNum, len(info.IndexUnicode)) 148 | fs, _ := os.Create(loadPath + name + "_byAddCharRedraw") 149 | err = info.Write(fs) 150 | if err != nil { 151 | panic(err) 152 | } 153 | err = fs.Close() 154 | if err != nil { 155 | panic(err) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /game/VM/model.go: -------------------------------------------------------------------------------- 1 | package VM 2 | 3 | import "lucksystem/game/enum" 4 | 5 | type Options struct { 6 | GameName string 7 | PluginFile string 8 | Mode enum.VMRunMode 9 | } 10 | -------------------------------------------------------------------------------- /game/VM/vm.go: -------------------------------------------------------------------------------- 1 | package VM 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "lucksystem/game/api" 8 | "lucksystem/game/engine" 9 | "lucksystem/game/enum" 10 | "lucksystem/game/operator" 11 | "lucksystem/game/runtime" 12 | "lucksystem/script" 13 | 14 | "github.com/golang/glog" 15 | ) 16 | 17 | type VM struct { 18 | *runtime.Runtime 19 | 20 | // 游戏对应操作接口 21 | Operate api.Operator 22 | 23 | Scripts map[string]*script.Script 24 | // 当前脚本名 25 | CScriptName string 26 | 27 | // 下一步执行下标 28 | CNext int 29 | // 下一步执行偏移 30 | EIP int 31 | } 32 | 33 | func NewVM(opts *Options) *VM { 34 | vm := &VM{ 35 | Scripts: make(map[string]*script.Script), 36 | } 37 | if len(opts.PluginFile) != 0 { 38 | vm.Operate = operator.NewPlugin(opts.PluginFile) 39 | } else { 40 | switch opts.GameName { 41 | case "LB_EN": 42 | vm.Operate = operator.NewLB_EN() 43 | case "SP": 44 | vm.Operate = operator.NewSP() 45 | } 46 | } 47 | vm.Runtime = runtime.NewRuntime(opts.Mode) 48 | vm.Operate.Init(vm.Runtime) 49 | return vm 50 | } 51 | 52 | func (vm *VM) LoadScript(scr *script.Script, _switch bool) { 53 | vm.Scripts[scr.Name] = scr 54 | if _switch { 55 | vm.SwitchScript(scr.Name) 56 | } 57 | } 58 | 59 | func (vm *VM) SwitchScript(name string) { 60 | if scr, ok := vm.Scripts[name]; ok { 61 | vm.CScriptName = scr.Name 62 | vm.Runtime.SwitchScript(scr) 63 | } 64 | } 65 | 66 | // 在对EIP修改后调用,查找下一条具体指令,返回指令序号 67 | func (vm *VM) findCode(oldEIP int) int { 68 | index := vm.CIndex 69 | _script := vm.Scripts[vm.CScriptName] 70 | if vm.EIP > oldEIP { 71 | // 向下查找 72 | for index < _script.CodeNum && _script.Codes[index].Pos < vm.EIP { 73 | index++ 74 | } 75 | if _script.Codes[index].Pos == vm.EIP { 76 | return index 77 | } else { 78 | panic(fmt.Sprintf("未找到跳转位置 [%d]%d -> %d", vm.CIndex, oldEIP, vm.EIP)) 79 | } 80 | } else if vm.EIP < oldEIP { 81 | // 向上查找 82 | for index >= 0 && _script.Codes[index].Pos > vm.EIP { 83 | index-- 84 | } 85 | if _script.Codes[index].Pos == vm.EIP { 86 | return index 87 | } else { 88 | panic(fmt.Sprintf("未找到跳转位置 [%d]%d -> %d", vm.CIndex, oldEIP, vm.EIP)) 89 | } 90 | } else { 91 | return index 92 | } 93 | 94 | } 95 | 96 | func (vm *VM) getNextPos() int { 97 | if vm.CIndex+1 >= vm.Scripts[vm.CScriptName].CodeNum { 98 | return 0 99 | } else { 100 | return vm.Scripts[vm.CScriptName].Codes[vm.CIndex+1].Pos 101 | } 102 | 103 | } 104 | 105 | func (vm *VM) Run() { 106 | if len(vm.OpcodeMap) == 0 { 107 | glog.Warning("OPCODE not loaded, import will not be supported") 108 | } 109 | glog.V(2).Infoln("Run: ", vm.CScriptName) 110 | vm.EIP = 0 111 | vm.CIndex = 0 112 | vm.CNext = 0 113 | 114 | var in []reflect.Value 115 | var code *script.CodeLine 116 | for { 117 | vm.CIndex = vm.CNext 118 | code = vm.Scripts[vm.CScriptName].Codes[vm.CIndex] 119 | opname := vm.Opcode(code.Opcode) 120 | vm.Runtime.Code().OpStr = opname 121 | operat := reflect.ValueOf(vm.Operate).MethodByName(opname) 122 | if operat.IsValid() { 123 | // 方法已定义,反射调用 124 | in = make([]reflect.Value, 1) 125 | in[0] = reflect.ValueOf(vm.Runtime) 126 | } else { 127 | // 方法未定义,调用UNDEFINE 128 | operat = reflect.ValueOf(vm.Operate).MethodByName("UNDEFINED") 129 | in = make([]reflect.Value, 2) 130 | in[0] = reflect.ValueOf(vm.Runtime) 131 | in[1] = reflect.ValueOf(opname) 132 | } 133 | glog.V(6).Infof("Index:%d Position:%d \n", vm.CIndex, code.Pos) 134 | fun := operat.Call(in) // 反射调用 operator,并返回一个function.HandlerFunc 135 | next := vm.getNextPos() // 取得下一句位置 136 | if fun[0].Kind() == reflect.Func { 137 | eip := 0 138 | if vm.RunMode == enum.VMRun { 139 | go fun[0].Interface().(engine.HandlerFunc)() // 调用,默认传递参数列表 140 | eip = <-vm.Runtime.ChanEIP // 取得跳转的位置 141 | } 142 | 143 | if eip > 0 { // 为0则默认下一句 144 | next = eip 145 | } 146 | } 147 | glog.V(6).Infoln("\tnext:", next) 148 | 149 | // if next == 0 || opname == "END" { - Many game scripts have an END opcode, but that does not mean that there is nothing else to parse after that opcode. 150 | if next == 0 { 151 | break // 结束 152 | } 153 | vm.EIP = next 154 | vm.CNext = vm.findCode(code.Pos) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /game/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | -------------------------------------------------------------------------------- /game/api/operater.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "lucksystem/game/engine" 5 | "lucksystem/game/runtime" 6 | ) 7 | 8 | // Operator 需定制指令 9 | type Operator interface { 10 | Init(ctx *runtime.Runtime) 11 | } 12 | 13 | // LucaOperator 通用指令 14 | type LucaOperator interface { 15 | LucaDefaultOperator 16 | LucaExprOperator 17 | LucaUndefinedOperator 18 | } 19 | 20 | type LucaDefaultOperator interface { 21 | UNKNOW0(ctx *runtime.Runtime) engine.HandlerFunc 22 | IFN(ctx *runtime.Runtime) engine.HandlerFunc 23 | IFY(ctx *runtime.Runtime) engine.HandlerFunc 24 | GOTO(ctx *runtime.Runtime) engine.HandlerFunc 25 | JUMP(ctx *runtime.Runtime) engine.HandlerFunc 26 | FARCALL(ctx *runtime.Runtime) engine.HandlerFunc 27 | MOVE(ctx *runtime.Runtime) engine.HandlerFunc 28 | } 29 | 30 | type LucaExprOperator interface { 31 | EQU(ctx *runtime.Runtime) engine.HandlerFunc 32 | EQUN(ctx *runtime.Runtime) engine.HandlerFunc 33 | ADD(ctx *runtime.Runtime) engine.HandlerFunc 34 | RANDOM(ctx *runtime.Runtime) engine.HandlerFunc 35 | } 36 | 37 | type LucaUndefinedOperator interface { 38 | UNDEFINED(ctx *runtime.Runtime, opname string) engine.HandlerFunc 39 | } 40 | -------------------------------------------------------------------------------- /game/engine/engine.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | type HandlerFunc func() 4 | type Engine struct { 5 | } 6 | -------------------------------------------------------------------------------- /game/engine/function.go: -------------------------------------------------------------------------------- 1 | // function 模拟器相关 2 | // 此包内的方法与模拟器相关,负责画面的输出、按键的输入等操作 3 | package engine 4 | 5 | import ( 6 | "github.com/golang/glog" 7 | ) 8 | 9 | func (Engine) FARCALL(params ...interface{}) int { 10 | if len(params) != 3 { 11 | panic("参数数量错误") 12 | } 13 | index := params[0].(uint16) 14 | fileStr := params[1].(string) 15 | jumpPos := params[2].(uint32) 16 | glog.V(3).Infof("Engine: FARCALL (%d) {goto \"%s\", %d}\n", index, fileStr, jumpPos) 17 | return 0 // 向下执行 18 | } 19 | 20 | func (Engine) JUMP(params ...interface{}) int { 21 | if len(params) != 2 { 22 | panic("参数数量错误") 23 | } 24 | 25 | fileStr := params[0].(string) 26 | jumpPos := params[1].(uint32) 27 | glog.V(3).Infof("Engine: JUMP {goto \"%s\", %d}\n", fileStr, jumpPos) 28 | return 0 // 向下执行 29 | } 30 | -------------------------------------------------------------------------------- /game/engine/function2.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "github.com/golang/glog" 5 | "strings" 6 | ) 7 | 8 | func (Engine) MESSAGE(params ...interface{}) int { 9 | if len(params) != 2 { 10 | panic("参数数量错误") 11 | } 12 | 13 | voiceId := params[0].(uint16) 14 | str := params[1].(string) 15 | glog.V(3).Infof(`MESSAGE (%d, "%s")\n`, voiceId, str) 16 | return 0 // 向下执行 17 | } 18 | 19 | func (Engine) SELECT(params ...interface{}) int { 20 | if len(params) != 1 { 21 | panic("参数数量错误") 22 | } 23 | 24 | selectStr := strings.Split(params[0].(string), "$d") 25 | 26 | selectID := 1 27 | glog.V(3).Infof(`SELECT (%v) %d\n`, selectStr, selectID) 28 | 29 | return selectID // 向下执行 30 | } 31 | -------------------------------------------------------------------------------- /game/enum/enum.go: -------------------------------------------------------------------------------- 1 | package enum 2 | 3 | type VMRunMode int8 4 | 5 | const ( 6 | VMRun VMRunMode = iota 7 | VMRunExport 8 | VMRunImport 9 | ) 10 | -------------------------------------------------------------------------------- /game/expr/expr.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | "strconv" 7 | ) 8 | 9 | const ( 10 | TOperator = 0 11 | TNumber = 1 12 | TVariable = 2 13 | ) 14 | 15 | type Token struct { 16 | Data string 17 | Type int 18 | } 19 | 20 | func RunExpr(exprStr string, variable map[string]int) (bool, error) { 21 | tokens, err := Parser(exprStr) 22 | if err != nil { 23 | return false, err 24 | } 25 | result, err := Exec(tokens, variable) 26 | if err != nil { 27 | return false, err 28 | } 29 | if result != 0 { 30 | return true, nil 31 | } 32 | return false, nil 33 | } 34 | 35 | func Exec(tokens []Token, variable map[string]int) (int, error) { 36 | stack := list.New() 37 | for _, token := range tokens { 38 | if token.Type == TVariable { 39 | val, has := variable[token.Data] 40 | if !has { 41 | // 变量不存在则添加为0 42 | variable[token.Data] = 0 43 | //return 0, errors.New(token.Data + " 变量不存在") 44 | } 45 | stack.PushBack(val) 46 | } else if token.Type == TNumber { 47 | val, err := strconv.Atoi(token.Data) 48 | if err != nil { 49 | return 0, err 50 | } 51 | stack.PushBack(val) 52 | } else if token.Type == TOperator { 53 | if stack.Len() < 2 { 54 | return 0, errors.New("表达式错误") 55 | } 56 | B := stack.Back() 57 | stack.Remove(B) 58 | A := stack.Back() 59 | stack.Remove(A) 60 | valA := A.Value.(int) 61 | valB := B.Value.(int) 62 | 63 | result := Calc(valA, valB, token.Data) 64 | stack.PushBack(result) 65 | } 66 | } 67 | result := stack.Back() 68 | return result.Value.(int), nil 69 | } 70 | 71 | // Parser 将字符串表达式转换为逆序表达式 72 | func Parser(exprStr string) (tokens []Token, err error) { 73 | tokens = make([]Token, 0, len(exprStr)/2) 74 | if exprStr[0] != '(' { 75 | exprStr = "(" + exprStr + ")" 76 | } 77 | stack := list.New() 78 | word := make([]byte, 0, 10) 79 | isNum := false 80 | for i := 0; i < len(exprStr); i++ { 81 | ch := exprStr[i] 82 | if ch == ' ' || IsOperator(ch) { // 单词读取结束(换行或读到操作符) 83 | sword := string(word) 84 | if len(word) > 0 { 85 | if isNum { 86 | tokens = append(tokens, Token{ 87 | Data: sword, 88 | Type: TNumber, 89 | }) 90 | } else { 91 | tokens = append(tokens, Token{ 92 | Data: sword, 93 | Type: TVariable, 94 | }) 95 | } 96 | isNum = false 97 | word = word[0:0] 98 | } 99 | if ch == ' ' { 100 | continue 101 | } 102 | word = append(word, ch) 103 | if i+1 < len(exprStr) && IsOperator2(ch, exprStr[i+1]) { 104 | word = append(word, exprStr[i+1]) 105 | i++ 106 | } 107 | sword = string(word) 108 | // 判断优先级 109 | if sword == ")" { 110 | top := stack.Back() 111 | for top != nil && top.Value.(string) != "(" { 112 | stack.Remove(top) 113 | tokens = append(tokens, Token{ 114 | Data: top.Value.(string), 115 | Type: TOperator, 116 | }) 117 | top = stack.Back() 118 | } 119 | stack.Remove(top) 120 | } else if sword == "(" { 121 | stack.PushBack(sword) 122 | } else { 123 | top := stack.Back() 124 | for top != nil && stack.Len() > 0 && (GetOperatorLevel(sword) <= GetOperatorLevel(top.Value.(string))) { 125 | stack.Remove(top) 126 | tokens = append(tokens, Token{ 127 | Data: top.Value.(string), 128 | Type: TOperator, 129 | }) 130 | top = stack.Back() 131 | } 132 | stack.PushBack(sword) 133 | } 134 | word = word[0:0] 135 | continue 136 | } 137 | if isNum { 138 | if (ch < '0' || ch > '9') && ch != '.' { 139 | return nil, errors.New("数字中包含非法字符") 140 | } 141 | } 142 | if len(word) == 0 { // 首个单词 143 | if ch >= '0' && ch <= '9' { //单词首字符为数字,则此单词为number型 144 | isNum = true 145 | } else { 146 | isNum = false 147 | } 148 | } 149 | word = append(word, ch) 150 | } 151 | 152 | top := stack.Back() 153 | for top != nil && stack.Len() > 0 { 154 | stack.Remove(top) 155 | 156 | tokens = append(tokens, Token{ 157 | Data: top.Value.(string), 158 | Type: TOperator, 159 | }) 160 | top = stack.Back() 161 | } 162 | return tokens, nil 163 | } 164 | -------------------------------------------------------------------------------- /game/expr/expr_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "container/list" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestRun(t *testing.T) { 10 | 11 | res, err := RunExpr("#5001+1==10", map[string]int{"#5001": 10}) 12 | if err != nil { 13 | fmt.Print(err) 14 | } 15 | fmt.Println(res) 16 | 17 | } 18 | func TestParser(t *testing.T) { 19 | 20 | tokens, _ := Parser("a>>b") 21 | for i, token := range tokens { 22 | fmt.Println(i, token.Data, token.Type) 23 | } 24 | 25 | } 26 | func TestStack(t *testing.T) { 27 | 28 | l := list.New() 29 | l.PushBack("a") 30 | l.PushBack("b") 31 | l.PushBack("c") 32 | 33 | tmp := l.Back() 34 | l.Remove(tmp) 35 | fmt.Println(tmp.Value.(string)) 36 | tmp = l.Back() 37 | l.Remove(tmp) 38 | fmt.Println(tmp.Value.(string)) 39 | tmp = l.Back() 40 | l.Remove(tmp) 41 | fmt.Println(tmp.Value.(string)) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /game/expr/utils.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | /* 4 | 判断单字符是否为操作符 5 | */ 6 | func IsOperator(ch byte) bool { 7 | op := "+-*/%()[]{}><=|&^!" 8 | for i := 0; i < len(op); i++ { 9 | if ch == op[i] { 10 | return true 11 | } 12 | } 13 | return false 14 | } 15 | 16 | /* 17 | 判断两个字符是否为组合操作符 18 | */ 19 | func IsOperator2(ch, ch2 byte) bool { 20 | if ch == '<' && ch2 == '<' { 21 | return true 22 | } else if ch == '>' && ch2 == '>' { 23 | return true 24 | } else if ch == '=' && ch2 == '=' { 25 | return true 26 | } else if ch == '!' && ch2 == '=' { 27 | return true 28 | } else if ch == '<' && ch2 == '=' { 29 | return true 30 | } else if ch == '>' && ch2 == '=' { 31 | return true 32 | } else if ch == '|' && ch2 == '|' { 33 | return true 34 | } else if ch == '&' && ch2 == '&' { 35 | return true 36 | } 37 | return false 38 | } 39 | 40 | func GetOperatorLevel(word string) int { 41 | 42 | level := [][]string{ 43 | {"*", "/", "%", ""}, 44 | {"+", "-", "", ""}, 45 | {"<<", ">>", "", ""}, 46 | {">", ">=", "<", "<="}, 47 | {"==", "!=", "", ""}, 48 | {"&", "", "", ""}, 49 | {"^", "", "", ""}, 50 | {"|", "", "", ""}, 51 | {"&&", "", "", ""}, 52 | {"||", "", "", ""}, 53 | {"=", "", "", ""}, 54 | } 55 | for i := 0; i < 11; i++ { 56 | for j := 0; j < 4; j++ { 57 | if len(level[i][j]) == 0 { 58 | break 59 | } 60 | if word == level[i][j] { 61 | return 10 - i 62 | } 63 | } 64 | } 65 | return -1 66 | 67 | } 68 | 69 | func Calc(A, B int, op string) int { 70 | switch op { 71 | case "+": 72 | return A + B 73 | case "-": 74 | return A - B 75 | case "*": 76 | return A * B 77 | case "/": 78 | return A / B 79 | case "%": 80 | return A % B 81 | case "&": 82 | return A & B 83 | case "^": 84 | return A ^ B 85 | case "|": 86 | return A | B 87 | case ">>": 88 | return A >> B 89 | case "<<": 90 | return A << B 91 | case "&&": 92 | if A != 0 && B != 0 { 93 | return 1 94 | } 95 | case "||": 96 | if A != 0 || B != 0 { 97 | return 1 98 | } 99 | case ">": 100 | if A > B { 101 | return 1 102 | } 103 | case "<": 104 | if A < B { 105 | return 1 106 | } 107 | case ">=": 108 | if A >= B { 109 | return 1 110 | } 111 | case "<=": 112 | if A <= B { 113 | return 1 114 | } 115 | case "==": 116 | if A == B { 117 | return 1 118 | } 119 | case "!=": 120 | if A != B { 121 | return 1 122 | } 123 | default: 124 | return 0 125 | } 126 | return 0 127 | } 128 | -------------------------------------------------------------------------------- /game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | 9 | "github.com/golang/glog" 10 | "lucksystem/charset" 11 | "lucksystem/game/VM" 12 | "lucksystem/game/enum" 13 | "lucksystem/pak" 14 | "lucksystem/script" 15 | ) 16 | 17 | const ( 18 | ResScript = "SCRIPT.PAK" 19 | ResScriptExt = ".txt" 20 | ) 21 | 22 | var ScriptBlackList = []string{"TEST", "_VOICEOTHER", "_VARNAME", "_VARNUM", "_CGMODE", "_SCR_LABEL", "_VOICE_PARAM", "_BUILD_COUNT", "_TASK", "_BUILD_TIME", "_VARSTRNAME"} 23 | 24 | type GameOptions struct { 25 | GameName string 26 | PluginFile string 27 | OpcodeFile string 28 | Version int 29 | ResourcesDir string 30 | Coding charset.Charset 31 | Mode enum.VMRunMode 32 | } 33 | 34 | type Game struct { 35 | Version int 36 | ResourcesDir string 37 | Coding charset.Charset 38 | Resources map[string]*pak.Pak 39 | 40 | VM *VM.VM 41 | ScriptList []string 42 | } 43 | 44 | func NewGame(opt *GameOptions) *Game { 45 | if opt.Coding == "" { 46 | opt.Coding = charset.UTF_8 47 | } 48 | game := &Game{ 49 | Version: opt.Version, 50 | ResourcesDir: opt.ResourcesDir, 51 | Coding: opt.Coding, 52 | Resources: make(map[string]*pak.Pak), 53 | VM: VM.NewVM(&VM.Options{ 54 | GameName: opt.GameName, 55 | Mode: opt.Mode, 56 | PluginFile: opt.PluginFile, 57 | }), 58 | } 59 | if len(opt.OpcodeFile) > 0 { 60 | game.VM.LoadOpcode(opt.OpcodeFile) 61 | } 62 | return game 63 | } 64 | 65 | func (g *Game) LoadResources() { 66 | g.LoadScriptResources(filepath.Join(g.ResourcesDir, ResScript)) 67 | g.load() 68 | } 69 | 70 | func (g *Game) LoadScriptResources(file string) { 71 | g.Resources[ResScript] = pak.LoadPak(file, g.Coding) 72 | g.load() 73 | } 74 | 75 | func (g *Game) load() { 76 | var err error 77 | for key, p := range g.Resources { 78 | switch key { 79 | case ResScript: 80 | var entry *pak.Entry 81 | for i := 1; i <= int(p.FileCount); i++ { 82 | entry, err = p.GetById(i) 83 | if err != nil { 84 | panic(err) 85 | } 86 | if !ScriptCanLoad(entry.Name) { 87 | glog.V(6).Infoln("Pass", entry.Name) 88 | continue 89 | } 90 | glog.V(6).Infof("%v %v\n", entry.Name, len(entry.Data)) 91 | scr := script.LoadScript(&script.LoadOptions{ 92 | Entry: entry, 93 | }) 94 | g.VM.LoadScript(scr, false) 95 | g.ScriptList = append(g.ScriptList, scr.Name) 96 | g.VM.ScriptNames[scr.Name] = struct{}{} 97 | glog.V(6).Infoln(scr.CodeNum) 98 | } 99 | } 100 | } 101 | } 102 | 103 | func (g *Game) RunScript() { 104 | for _, name := range g.ScriptList { 105 | g.VM.SwitchScript(name) 106 | g.VM.Run() 107 | } 108 | for _, name := range g.ScriptList { 109 | labels, gotos := g.VM.GetMaps(name) 110 | g.VM.Scripts[name].SetGlobalLabel(labels) 111 | g.VM.Scripts[name].SetGlobalGoto(gotos) 112 | } 113 | 114 | } 115 | 116 | func (g *Game) ExportScript(dir string, noSubDir bool) { 117 | if !noSubDir { 118 | dir = path.Join(dir, ResScript) 119 | } 120 | exist, isDir := IsExistDir(dir) 121 | if exist && !isDir { 122 | panic("已存在同名文件") 123 | } 124 | if !exist { 125 | os.MkdirAll(dir, os.ModePerm) 126 | } 127 | for _, name := range g.ScriptList { 128 | f, err := os.Create(path.Join(dir, name+ResScriptExt)) 129 | if err != nil { 130 | panic(err) 131 | } 132 | err = g.VM.Scripts[name].Export(f) 133 | if err != nil { 134 | panic(err) 135 | } 136 | f.Close() 137 | } 138 | 139 | //for i := 1; i < len(g.VM.GlobalLabelGoto)+1; i++ { 140 | // fmt.Println(i, " ", g.VM.GlobalLabelGoto[i]) 141 | //} 142 | } 143 | 144 | func (g *Game) ImportScript(dir string, noSubDir bool) { 145 | if !noSubDir { 146 | dir = path.Join(dir, ResScript) 147 | } 148 | for _, name := range g.ScriptList { 149 | f, err := os.Open(path.Join(dir, name+ResScriptExt)) 150 | if err != nil { 151 | panic(err) 152 | } 153 | err = g.VM.Scripts[name].Import(f) 154 | if err != nil { 155 | panic(err) 156 | } 157 | f.Close() 158 | } 159 | } 160 | 161 | func (g *Game) ImportScriptWrite(out string) { 162 | for _, name := range g.ScriptList { 163 | g.VM.AddGlobalLabelMap(g.VM.Scripts[name].IGlobalLabelMap) 164 | } 165 | var err error 166 | for _, name := range g.ScriptList { 167 | g.VM.Scripts[name].SetImportGlobalLabel(g.VM.IGlobalLabelMap) 168 | w := bytes.NewBuffer(nil) 169 | err = g.VM.Scripts[name].Write(w) 170 | if err != nil { 171 | panic(err) 172 | } 173 | err = g.Resources[ResScript].Set(name, w) 174 | if err != nil { 175 | panic(err) 176 | } 177 | w.Reset() 178 | } 179 | 180 | f, _ := os.Create(out) 181 | g.Resources[ResScript].Write(f) 182 | f.Close() 183 | } 184 | -------------------------------------------------------------------------------- /game/game_test.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | 8 | "lucksystem/charset" 9 | "lucksystem/game/enum" 10 | 11 | "github.com/go-restruct/restruct" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | flag.Set("alsologtostderr", "true") 16 | flag.Set("log_dir", "log") 17 | flag.Set("v", "5") 18 | flag.Parse() 19 | 20 | ret := m.Run() 21 | os.Exit(ret) 22 | } 23 | 24 | func TestLoadPak(t *testing.T) { 25 | restruct.EnableExprBeta() 26 | game := NewGame(&GameOptions{ 27 | GameName: "SP", 28 | PluginFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/SP.py", 29 | OpcodeFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/SP/OPCODE.txt", 30 | ResourcesDir: "C:/Users/wetor/Desktop/Prototype", 31 | Coding: charset.ShiftJIS, 32 | Mode: enum.VMRunExport, 33 | }) 34 | game.LoadResources() 35 | game.RunScript() 36 | 37 | game.ExportScript("C:/Users/wetor/Desktop/Prototype/Export", false) 38 | 39 | } 40 | 41 | func TestLoadPak2(t *testing.T) { 42 | restruct.EnableExprBeta() 43 | game := NewGame(&GameOptions{ 44 | GameName: "SP", 45 | PluginFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/SP.py", 46 | OpcodeFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/SP/OPCODE.txt", 47 | ResourcesDir: "C:/Users/wetor/Desktop/Prototype", 48 | Coding: charset.ShiftJIS, 49 | Mode: enum.VMRunImport, 50 | }) 51 | game.LoadResources() 52 | game.ImportScript("C:/Users/wetor/Desktop/Prototype/Export", false) 53 | game.RunScript() 54 | 55 | game.ImportScriptWrite("C:/Users/wetor/Desktop/Prototype/Import/SCRIPT.PAK") 56 | } 57 | -------------------------------------------------------------------------------- /game/operator/SP.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "github.com/golang/glog" 5 | "lucksystem/charset" 6 | "lucksystem/game/engine" 7 | "lucksystem/game/runtime" 8 | ) 9 | 10 | type SP struct { 11 | LucaOperateUndefined 12 | LucaOperateDefault 13 | LucaOperateExpr 14 | } 15 | 16 | func NewSP() *SP { 17 | return &SP{} 18 | } 19 | 20 | func (g *SP) Init(ctx *runtime.Runtime) { 21 | ctx.Init(charset.ShiftJIS, charset.Unicode, true) 22 | } 23 | 24 | func (g *SP) MESSAGE(ctx *runtime.Runtime) engine.HandlerFunc { 25 | code := ctx.Code() 26 | op := NewOP(ctx, code.ParamBytes, 0) 27 | 28 | var voiceId = op.ReadUInt16(true) 29 | var msgStr = op.ReadLString(true, ctx.TextCharset) 30 | op.ReadUInt8(false) 31 | op.SetOperateParams() 32 | 33 | return func() { 34 | // 这里是执行内容 35 | ctx.Engine.MESSAGE(voiceId, msgStr) 36 | ctx.ChanEIP <- 0 37 | } 38 | 39 | } 40 | func (g *SP) SELECT(ctx *runtime.Runtime) engine.HandlerFunc { 41 | code := ctx.Code() 42 | op := NewOP(ctx, code.ParamBytes, 0) 43 | 44 | var varID = op.ReadUInt16(true) 45 | op.ReadUInt16(false) 46 | op.ReadUInt16(false) 47 | op.ReadUInt16(false) 48 | var msgStr = op.ReadLString(true, ctx.TextCharset) 49 | op.ReadUInt16(false) 50 | op.ReadUInt16(false) 51 | op.ReadUInt16(false) 52 | op.SetOperateParams() 53 | 54 | return func() { 55 | 56 | selectID := ctx.Engine.SELECT(msgStr) 57 | 58 | //fun.Call(&varID, msgStr_en) 59 | glog.V(3).Infof("SELECT #%d = %d\n", varID, selectID) 60 | ctx.ChanEIP <- 0 61 | } 62 | } 63 | 64 | func (g *SP) IMAGELOAD(ctx *runtime.Runtime) engine.HandlerFunc { 65 | code := ctx.Code() 66 | op := NewOP(ctx, code.ParamBytes, 0) 67 | 68 | var mode = op.ReadUInt16(true) 69 | op.ReadUInt16(true) 70 | 71 | if mode == 1795 { 72 | op.ReadUInt8(true) 73 | } else { 74 | op.ReadUInt16(true) 75 | op.ReadUInt16(true) 76 | op.ReadUInt16(true) 77 | } 78 | 79 | if mode == 1 { 80 | // 立绘 81 | op.ReadUInt16(true) 82 | } else if mode == 1795 { 83 | } else { 84 | // 其他 85 | } 86 | op.SetOperateParams() 87 | return func() { 88 | // 这里是执行 与虚拟机逻辑有关的代码 89 | 90 | // 下一步执行地址,为0则表示紧接着向下 91 | ctx.ChanEIP <- 0 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /game/operator/default_operate.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "lucksystem/game/engine" 5 | "lucksystem/game/runtime" 6 | ) 7 | 8 | type LucaOperateDefault struct { 9 | } 10 | 11 | //func (g *LucaOperateDefault) UNKNOW0(ctx *runtime.Runtime) engine.HandlerFunc { 12 | // code := ctx.Code() 13 | // var value uint16 14 | // var exprStr string 15 | // 16 | // next := GetParam(code.ParamBytes, &value) 17 | // GetParam(code.ParamBytes, &exprStr, next, 0, ctx.ExprCharset) 18 | // ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 19 | // value, 20 | // exprStr, 21 | // ctx.ExprCharset, 22 | // ) 23 | // return func() { 24 | // // 这里是执行 与虚拟机逻辑有关的代码 25 | // 26 | // // 下一步执行地址,为0则表示紧接着向下 27 | // ctx.ChanEIP <- 0 28 | // } 29 | //} 30 | 31 | func (g *LucaOperateDefault) IFN(ctx *runtime.Runtime) engine.HandlerFunc { 32 | code := ctx.Code() 33 | 34 | op := NewOP(ctx, code.ParamBytes, 0) 35 | op.ReadString(true, ctx.ExprCharset) 36 | op.ReadJump(true) 37 | op.SetOperateParams() 38 | 39 | return func() { 40 | // 这里是执行 与虚拟机逻辑有关的代码 41 | //eip := 0 42 | // 43 | //res, err := ctx.Variable.TestExpr(exprStr) 44 | //if err != nil { 45 | // panic(err) 46 | //} 47 | //if !res { 48 | // glog.V(3).Infof("IFN %s => %d\n", exprStr, !res) 49 | // eip = int(jumpPos) 50 | //} 51 | // 这里执行与游戏相关代码,内部与虚拟机无关联 52 | 53 | ctx.ChanEIP <- 0 54 | } 55 | } 56 | func (g *LucaOperateDefault) IFY(ctx *runtime.Runtime) engine.HandlerFunc { 57 | code := ctx.Code() 58 | op := NewOP(ctx, code.ParamBytes, 0) 59 | op.ReadString(true, ctx.ExprCharset) 60 | op.ReadJump(true) 61 | op.SetOperateParams() 62 | 63 | return func() { 64 | // 这里是执行 与虚拟机逻辑有关的代码 65 | // 这里执行与游戏相关代码,内部与虚拟机无关联 66 | 67 | ctx.ChanEIP <- 0 68 | } 69 | } 70 | func (g *LucaOperateDefault) FARCALL(ctx *runtime.Runtime) engine.HandlerFunc { 71 | code := ctx.Code() 72 | 73 | op := NewOP(ctx, code.ParamBytes, 0) 74 | index := op.ReadUInt16(true) 75 | fileStr := op.ReadString(true, ctx.ExprCharset) 76 | jumpPos := op.ReadFileJump(true, fileStr) 77 | op.SetOperateParams() 78 | 79 | return func() { 80 | // 这里是执行内容 81 | 82 | ctx.Engine.FARCALL(index, fileStr, jumpPos) 83 | ctx.ChanEIP <- 0 84 | } 85 | } 86 | 87 | func (g *LucaOperateDefault) GOTO(ctx *runtime.Runtime) engine.HandlerFunc { 88 | code := ctx.Code() 89 | op := NewOP(ctx, code.ParamBytes, 0) 90 | op.ReadJump(true) 91 | op.SetOperateParams() 92 | 93 | return func() { 94 | // 这里是执行内容 95 | ctx.ChanEIP <- 0 96 | } 97 | } 98 | 99 | func (g *LucaOperateDefault) GOSUB(ctx *runtime.Runtime) engine.HandlerFunc { 100 | code := ctx.Code() 101 | op := NewOP(ctx, code.ParamBytes, 0) 102 | op.ReadUInt16(true) 103 | op.ReadJump(true) 104 | op.SetOperateParams() 105 | 106 | return func() { 107 | // 这里是执行内容 108 | ctx.ChanEIP <- 0 109 | } 110 | } 111 | 112 | func (g *LucaOperateDefault) JUMP(ctx *runtime.Runtime) engine.HandlerFunc { 113 | code := ctx.Code() 114 | var fileStr string 115 | var jumpPos uint32 116 | op := NewOP(ctx, code.ParamBytes, 0) 117 | fileStr = op.ReadString(true, ctx.ExprCharset) 118 | if op.CanRead() { 119 | jumpPos = op.ReadFileJump(true, fileStr) 120 | } 121 | op.SetOperateParams() 122 | 123 | // ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, &script.JumpParam{ 124 | // ScriptName: fileStr, 125 | // Position: int(jumpPos), 126 | // }) 127 | return func() { 128 | // 这里是执行内容 129 | 130 | ctx.Engine.JUMP(fileStr, jumpPos) 131 | ctx.ChanEIP <- 0 132 | } 133 | 134 | } 135 | 136 | //func (g *LucaOperateDefault) MOVE(ctx *runtime.Runtime) engine.HandlerFunc { 137 | // code := ctx.Code() 138 | // var val1 uint8 139 | // var val2 uint16 140 | // var val3 uint16 141 | // var height uint16 142 | // var width uint16 143 | // 144 | // next := GetParam(code.ParamBytes, &val1) 145 | // next = GetParam(code.ParamBytes, &val2, next) 146 | // next = GetParam(code.ParamBytes, &val3, next) 147 | // next = GetParam(code.ParamBytes, &height, next) 148 | // GetParam(code.ParamBytes, &width, next) 149 | // ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 150 | // val1, 151 | // val2, 152 | // val3, 153 | // height, 154 | // width, 155 | // ) 156 | // return func() { 157 | // // 这里是执行 与虚拟机逻辑有关的代码 158 | // 159 | // // 下一步执行地址,为0则表示紧接着向下 160 | // ctx.ChanEIP <- 0 161 | // } 162 | //} 163 | -------------------------------------------------------------------------------- /game/operator/expr_operate.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "lucksystem/game/engine" 5 | "lucksystem/game/runtime" 6 | ) 7 | 8 | type LucaOperateExpr struct { 9 | } 10 | 11 | func (g *LucaOperateExpr) EQU(ctx *runtime.Runtime) engine.HandlerFunc { 12 | code := ctx.Code() 13 | var key uint16 14 | var value uint16 15 | 16 | next := GetParam(code.ParamBytes, &key) 17 | if next < len(code.ParamBytes) { 18 | GetParam(code.ParamBytes, &value, next) 19 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 20 | key, 21 | value, 22 | ) 23 | } else { 24 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 25 | key, 26 | ) 27 | } 28 | 29 | //utils.Logf("EQU #%d = %d", key, value) 30 | return func() { 31 | // 这里是执行 与虚拟机逻辑有关的代码 32 | //var keyStr string 33 | //if key <= 1 { 34 | // keyStr = ToString("%d", key) 35 | //} else { 36 | // keyStr = ToString("#%d", key) 37 | //} 38 | //ctx.Variable.Set(keyStr, int(value)) 39 | 40 | // 下一步执行地址,为0则表示紧接着向下 41 | ctx.ChanEIP <- 0 42 | } 43 | } 44 | 45 | // EQUN 等价于EQU 46 | func (g *LucaOperateExpr) EQUN(ctx *runtime.Runtime) engine.HandlerFunc { 47 | code := ctx.Code() 48 | var key uint16 49 | var value uint16 50 | 51 | next := GetParam(code.ParamBytes, &key) 52 | if next < len(code.ParamBytes) { 53 | GetParam(code.ParamBytes, &value, next) 54 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 55 | key, 56 | value, 57 | ) 58 | } else { 59 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 60 | key, 61 | ) 62 | } 63 | return func() { 64 | // 这里是执行 与虚拟机逻辑有关的代码 65 | //var keyStr string 66 | //if key <= 1 { 67 | // keyStr = ToString("%d", key) 68 | //} else { 69 | // keyStr = ToString("#%d", key) 70 | //} 71 | //ctx.Variable.Set(keyStr, int(value)) 72 | // 下一步执行地址,为0则表示紧接着向下 73 | ctx.ChanEIP <- 0 74 | } 75 | } 76 | 77 | func (g *LucaOperateExpr) ADD(ctx *runtime.Runtime) engine.HandlerFunc { 78 | code := ctx.Code() 79 | var value uint16 80 | var exprStr string 81 | 82 | next := GetParam(code.ParamBytes, &value) 83 | GetParam(code.ParamBytes, &exprStr, next, 0, ctx.ExprCharset) 84 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 85 | value, 86 | exprStr, 87 | ctx.ExprCharset, 88 | ) 89 | return func() { 90 | // 这里是执行 与虚拟机逻辑有关的代码 91 | 92 | // 下一步执行地址,为0则表示紧接着向下 93 | ctx.ChanEIP <- 0 94 | } 95 | } 96 | func (g *LucaOperateExpr) RANDOM(ctx *runtime.Runtime) engine.HandlerFunc { 97 | code := ctx.Code() 98 | var value uint16 99 | var lowerStr string 100 | var upperStr string 101 | 102 | next := GetParam(code.ParamBytes, &value) 103 | next = GetParam(code.ParamBytes, &lowerStr, next, 0, ctx.ExprCharset) 104 | GetParam(code.ParamBytes, &upperStr, next, 0, ctx.ExprCharset) 105 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 106 | value, 107 | lowerStr, 108 | upperStr, 109 | ctx.ExprCharset, 110 | ) 111 | return func() { 112 | // 这里是执行 与虚拟机逻辑有关的代码 113 | 114 | // 下一步执行地址,为0则表示紧接着向下 115 | ctx.ChanEIP <- 0 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /game/operator/opcode.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "strings" 5 | 6 | "lucksystem/charset" 7 | "lucksystem/game/runtime" 8 | "lucksystem/script" 9 | ) 10 | 11 | type Param struct { 12 | Type string 13 | Value interface{} 14 | Export bool 15 | } 16 | 17 | type OP struct { 18 | ctx *runtime.Runtime 19 | data []byte 20 | offset int 21 | params []Param 22 | index int 23 | } 24 | 25 | func NewOP(ctx *runtime.Runtime, data []byte, offset int) *OP { 26 | return &OP{ 27 | ctx: ctx, 28 | data: data, 29 | offset: offset, 30 | params: make([]Param, 0, 8), 31 | index: 0, 32 | } 33 | } 34 | 35 | func (op *OP) CanRead() bool { 36 | return op.offset < len(op.data) 37 | } 38 | 39 | func (op *OP) Read(export bool) []uint16 { 40 | val, end := AllToUint16(op.data[op.offset:]) 41 | 42 | for _, v := range val { 43 | op.params = append(op.params, Param{ 44 | Type: "uint16", 45 | Value: v, 46 | Export: export, 47 | }) 48 | op.index++ 49 | } 50 | if end >= 0 { 51 | op.offset += end 52 | op.params = append(op.params, Param{ 53 | Type: "uint8", 54 | Value: op.data[op.offset], 55 | Export: export, 56 | }) 57 | op.index++ 58 | 59 | } else { 60 | op.offset = len(op.data) 61 | } 62 | return val 63 | } 64 | 65 | func (op *OP) ReadUInt8(export bool) uint8 { 66 | var val uint8 67 | op.offset = GetParam(op.data, &val, op.offset) 68 | op.params = append(op.params, Param{ 69 | Type: "uint8", 70 | Value: val, 71 | Export: export, 72 | }) 73 | op.index++ 74 | return val 75 | } 76 | 77 | func (op *OP) ReadUInt16(export bool) uint16 { 78 | var val uint16 79 | op.offset = GetParam(op.data, &val, op.offset) 80 | op.params = append(op.params, Param{ 81 | Type: "uint16", 82 | Value: val, 83 | Export: export, 84 | }) 85 | op.index++ 86 | return val 87 | } 88 | 89 | func (op *OP) ReadUInt32(export bool) uint32 { 90 | var val uint32 91 | op.offset = GetParam(op.data, &val, op.offset) 92 | op.params = append(op.params, Param{ 93 | Type: "uint32", 94 | Value: val, 95 | Export: export, 96 | }) 97 | op.index++ 98 | return val 99 | } 100 | 101 | func (op *OP) ReadString(export bool, charset charset.Charset) string { 102 | var val string 103 | op.offset = GetParam(op.data, &val, op.offset, 0, charset) 104 | op.params = append(op.params, Param{ 105 | Type: "string", 106 | Value: &script.StringParam{ 107 | Data: val, 108 | Coding: charset, 109 | HasLen: false, 110 | }, 111 | Export: export, 112 | }) 113 | op.index++ 114 | return val 115 | } 116 | 117 | func (op *OP) ReadLString(export bool, charset charset.Charset) string { 118 | var val lstring 119 | op.offset = GetParam(op.data, &val, op.offset, 0, charset) 120 | op.params = append(op.params, Param{ 121 | Type: "lstring", 122 | Value: &script.StringParam{ 123 | Data: string(val), 124 | Coding: charset, 125 | HasLen: true, 126 | }, 127 | Export: export, 128 | }) 129 | op.index++ 130 | return string(val) 131 | } 132 | 133 | func (op *OP) ReadJump(export bool) uint32 { 134 | var val uint32 135 | op.offset = GetParam(op.data, &val, op.offset) 136 | op.params = append(op.params, Param{ 137 | Type: "jump", 138 | Value: &script.JumpParam{ 139 | Position: int(val), 140 | }, 141 | Export: export, 142 | }) 143 | op.index++ 144 | return val 145 | } 146 | 147 | func (op *OP) ReadFileJump(export bool, file string) uint32 { 148 | var val uint32 149 | op.offset = GetParam(op.data, &val, op.offset) 150 | param := &script.JumpParam{ 151 | ScriptName: file, 152 | Position: int(val), 153 | } 154 | if file != op.ctx.Script.Name && 155 | strings.ToUpper(file) != op.ctx.Script.Name && 156 | strings.ToLower(file) != op.ctx.Script.Name { 157 | param.GlobalIndex = op.ctx.AddLabel(file, op.ctx.CIndex, int(val)) 158 | } 159 | op.params = append(op.params, Param{ 160 | Type: "filejump", 161 | Value: param, 162 | Export: export, 163 | }) 164 | op.index++ 165 | return val 166 | } 167 | 168 | func (op *OP) SetOperateParams() { 169 | params := make([]interface{}, len(op.params), len(op.params)+1) 170 | requires := make([]bool, len(op.params)) 171 | // types := make([]string, len(op.params)) 172 | for i, param := range op.params { 173 | params[i] = param.Value 174 | requires[i] = param.Export 175 | // types[i] = param.Type 176 | } 177 | params = append(params, requires) 178 | _ = op.ctx.Script.SetOperateParams(op.ctx.CIndex, op.ctx.RunMode, params...) 179 | } 180 | -------------------------------------------------------------------------------- /game/operator/plugin.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/go-python/gpython/py" 7 | _ "github.com/go-python/gpython/stdlib" 8 | "lucksystem/charset" 9 | "lucksystem/game/engine" 10 | "lucksystem/game/runtime" 11 | ) 12 | 13 | type Plugin struct { 14 | file string 15 | ctx py.Context 16 | module *py.Module 17 | } 18 | 19 | func NewPlugin(file string) *Plugin { 20 | p := &Plugin{ 21 | file: file, 22 | ctx: py.NewContext(py.ContextOpts{ 23 | SysPaths: []string{"."}, 24 | }), 25 | } 26 | var err error 27 | p.module, err = py.RunFile(p.ctx, p.file, py.CompileOpts{ 28 | CurDir: "/", 29 | }, nil) 30 | if err != nil { 31 | py.TracebackDump(err) 32 | //glog.V(3).Infof("SELECT #%d = %d\n", varID, selectID) 33 | } 34 | return p 35 | } 36 | 37 | func (g *Plugin) Init(ctx *runtime.Runtime) { 38 | ctx.Init(charset.ShiftJIS, charset.Unicode, true) 39 | pluginContext.ctx = ctx 40 | call, ok := g.module.Globals["Init"] 41 | if ok { 42 | _, err := py.Call(call, nil, nil) 43 | if err != nil { 44 | py.TracebackDump(err) 45 | } 46 | } 47 | } 48 | 49 | func (g *Plugin) UNDEFINED(ctx *runtime.Runtime, opcode string) engine.HandlerFunc { 50 | // plugin 51 | if strings.HasPrefix(opcode, "0x") { 52 | if opcodeMap, ok := g.module.Globals["opcode_dict"]; ok { 53 | if dict, ok := opcodeMap.(py.StringDict); ok { 54 | if val, ok := dict[opcode]; ok { 55 | opcode = string(val.(py.String)) 56 | } 57 | } 58 | } 59 | } 60 | 61 | if call, ok := g.module.Globals[opcode]; ok { 62 | pluginContext.NewOP(ctx) 63 | _, err := py.Call(call, nil, nil) 64 | if err != nil { 65 | py.TracebackDump(err) 66 | } 67 | } else { 68 | code := ctx.Code() 69 | op := NewOP(ctx, code.ParamBytes, 0) 70 | op.Read(ctx.DefaultExport) 71 | op.SetOperateParams() 72 | } 73 | return func() { 74 | // 下一步执行地址,为0则表示紧接着向下 75 | ctx.ChanEIP <- 0 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /game/operator/plugin_opcode.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "github.com/go-python/gpython/py" 5 | "lucksystem/charset" 6 | "lucksystem/game/runtime" 7 | ) 8 | 9 | var pluginContext *PluginContext 10 | 11 | type PluginContext struct { 12 | ctx *runtime.Runtime 13 | op *OP 14 | } 15 | 16 | func (p *PluginContext) NewOP(ctx *runtime.Runtime) { 17 | p.ctx = ctx 18 | p.op = NewOP(p.ctx, p.ctx.Code().ParamBytes, 0) 19 | } 20 | 21 | func (p *PluginContext) Read(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 22 | var export py.Object = py.Bool(false) 23 | kwlist := []string{"export"} 24 | err := py.ParseTupleAndKeywords(args, kwargs, "|O:read", kwlist, &export) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | results := p.op.Read(bool(export.(py.Bool))) 30 | res := make(py.Tuple, len(results)) 31 | for i, result := range results { 32 | res[i] = py.Int(result) 33 | } 34 | return res, nil 35 | } 36 | 37 | func (p *PluginContext) ReadUInt8(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 38 | var export py.Object = py.Bool(false) 39 | kwlist := []string{"export"} 40 | err := py.ParseTupleAndKeywords(args, kwargs, "|O:read_uint8", kwlist, &export) 41 | if err != nil { 42 | return nil, err 43 | } 44 | result := p.op.ReadUInt8(bool(export.(py.Bool))) 45 | return py.Int(result), nil 46 | } 47 | 48 | func (p *PluginContext) ReadUInt16(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 49 | var export py.Object = py.Bool(false) 50 | kwlist := []string{"export"} 51 | err := py.ParseTupleAndKeywords(args, kwargs, "|O:read_uint16", kwlist, &export) 52 | if err != nil { 53 | return nil, err 54 | } 55 | result := p.op.ReadUInt16(bool(export.(py.Bool))) 56 | return py.Int(result), nil 57 | } 58 | 59 | func (p *PluginContext) ReadUInt32(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 60 | var export py.Object = py.Bool(false) 61 | kwlist := []string{"export"} 62 | err := py.ParseTupleAndKeywords(args, kwargs, "|O:read_uint32", kwlist, &export) 63 | if err != nil { 64 | return nil, err 65 | } 66 | result := p.op.ReadUInt32(bool(export.(py.Bool))) 67 | return py.Int(result), nil 68 | } 69 | 70 | func (p *PluginContext) ReadJump(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 71 | var file py.Object = py.String("") 72 | var export py.Object = py.Bool(true) 73 | kwlist := []string{"file", "export"} 74 | err := py.ParseTupleAndKeywords(args, kwargs, "|OO:read_jump", kwlist, &file, &export) 75 | if err != nil { 76 | return nil, err 77 | } 78 | var result uint32 79 | if len(file.(py.String)) > 0 { 80 | result = p.op.ReadFileJump(bool(export.(py.Bool)), string(file.(py.String))) 81 | } else { 82 | result = p.op.ReadJump(bool(export.(py.Bool))) 83 | } 84 | return py.Int(result), nil 85 | } 86 | 87 | func (p *PluginContext) ReadString(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 88 | var _charset py.Object = py.String(p.ctx.TextCharset) 89 | var export py.Object = py.Bool(true) 90 | kwlist := []string{"charset", "export"} 91 | err := py.ParseTupleAndKeywords(args, kwargs, "|OO:read_str", kwlist, &_charset, &export) 92 | if err != nil { 93 | return nil, err 94 | } 95 | result := p.op.ReadString(bool(export.(py.Bool)), charset.Charset(_charset.(py.String))) 96 | return py.String(result), nil 97 | } 98 | 99 | func (p *PluginContext) ReadLenString(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 100 | var _charset py.Object = py.String(p.ctx.TextCharset) 101 | var export py.Object = py.Bool(true) 102 | kwlist := []string{"charset", "export"} 103 | err := py.ParseTupleAndKeywords(args, kwargs, "|OO:read_len_str", kwlist, &_charset, &export) 104 | if err != nil { 105 | return nil, err 106 | } 107 | result := p.op.ReadLString(bool(export.(py.Bool)), charset.Charset(_charset.(py.String))) 108 | return py.String(result), nil 109 | } 110 | 111 | func (p *PluginContext) End(self py.Object, args py.Tuple) (py.Object, error) { 112 | p.op.SetOperateParams() 113 | return nil, nil 114 | } 115 | 116 | func (p *PluginContext) CanRead(self py.Object, args py.Tuple) (py.Object, error) { 117 | return py.Bool(p.op.CanRead()), nil 118 | } 119 | 120 | func (p *PluginContext) SetConfig(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { 121 | var exprCharset py.Object 122 | var textCharset py.Object 123 | var defaultExport py.Object = py.Bool(true) 124 | kwlist := []string{"expr_charset", "text_charset", "default_export"} 125 | err := py.ParseTupleAndKeywords(args, kwargs, "OO|O:set_config", kwlist, &exprCharset, &textCharset, &defaultExport) 126 | if err != nil { 127 | return nil, err 128 | } 129 | p.ctx.Init( 130 | charset.Charset(exprCharset.(py.String)), 131 | charset.Charset(textCharset.(py.String)), 132 | bool(defaultExport.(py.Bool))) 133 | 134 | mod := self.(*py.Module) 135 | mod.Globals["expr"] = exprCharset 136 | mod.Globals["text"] = textCharset 137 | return nil, nil 138 | } 139 | 140 | func init() { 141 | pluginContext = &PluginContext{} 142 | 143 | methods := []*py.Method{ 144 | py.MustNewMethod("read", pluginContext.Read, 0, `read(export=False) -> list(int)`), 145 | py.MustNewMethod("read_uint8", pluginContext.ReadUInt8, 0, `read_uint8(export=False) -> int`), 146 | py.MustNewMethod("read_uint16", pluginContext.ReadUInt16, 0, `read_uint16(export=False) -> int`), 147 | py.MustNewMethod("read_uint32", pluginContext.ReadUInt32, 0, `read_uint32(export=False) -> int`), 148 | py.MustNewMethod("read_jump", pluginContext.ReadJump, 0, `read_jump(file='', export=True) -> int`), 149 | py.MustNewMethod("read_str", pluginContext.ReadString, 0, `read_str(charset=textCharset, export=True) -> str`), 150 | py.MustNewMethod("read_len_str", pluginContext.ReadLenString, 0, `read_len_str(charset=textCharset, export=True) -> str`), 151 | py.MustNewMethod("end", pluginContext.End, 0, `end()`), 152 | py.MustNewMethod("can_read", pluginContext.CanRead, 0, `can_read() -> bool`), 153 | py.MustNewMethod("set_config", pluginContext.SetConfig, 0, `set_config(expr_charset, text_charset, default_export=True)`), 154 | } 155 | 156 | py.RegisterModule(&py.ModuleImpl{ 157 | Info: py.ModuleInfo{ 158 | Name: "core", 159 | Doc: "Core Module", 160 | }, 161 | Methods: methods, 162 | Globals: py.StringDict{ 163 | "Charset_UTF8": py.String(charset.UTF_8), 164 | "Charset_Unicode": py.String(charset.Unicode), 165 | "Charset_SJIS": py.String(charset.ShiftJIS), 166 | }, 167 | }) 168 | } 169 | -------------------------------------------------------------------------------- /game/operator/undefined_operate.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "github.com/golang/glog" 5 | "lucksystem/game/engine" 6 | "lucksystem/game/runtime" 7 | ) 8 | 9 | type LucaOperateUndefined struct { 10 | } 11 | 12 | func (g *LucaOperateUndefined) UNDEFINED(ctx *runtime.Runtime, opcode string) engine.HandlerFunc { 13 | glog.V(5).Infoln(ctx.CIndex, "Operation不存在", opcode) 14 | code := ctx.Code() 15 | if len(opcode) == 0 { 16 | opcode = ToString("%X", code.Opcode) 17 | } 18 | list, end := AllToUint16(code.ParamBytes) 19 | if end >= 0 { 20 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 21 | list, 22 | code.ParamBytes[end], 23 | ) 24 | } else { 25 | ctx.Script.SetOperateParams(ctx.CIndex, ctx.RunMode, 26 | list, 27 | ) 28 | } 29 | return func() { 30 | // 下一步执行地址,为0则表示紧接着向下 31 | ctx.ChanEIP <- 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /game/operator/util.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "lucksystem/charset" 8 | ) 9 | 10 | type lstring string // len + string 11 | 12 | // DecodeString 从指定位置读取一个指定编码字符串 13 | // 以"0x00 0x00"结尾 14 | // 1.bytes 要读取的字节数据 15 | // 2.start 开始位置 16 | // 3.slen 不包含EOF的字符串字节长度,为0则读取到EOF 17 | // 4.coding 源编码 18 | // return 19 | // 1.string 转码后(utf8)的字符串 20 | // 2.uint16 读取完毕后的字节位置,结尾已跳过 21 | // charset 22 | // 1.UTF-8 1~3byte一字,EOF为0x00 23 | // 2.ShiftJIS 1~2byte一字,EOF为0x00 24 | // 3.Unicode(UTF-16LE) 2byte一字,EOF为0x00 0x00 25 | 26 | func DecodeString(bytes []byte, start, slen int, coding charset.Charset) (string, int) { 27 | end := start 28 | eofLen := 0 // 29 | charLen := 0 30 | 31 | switch coding { 32 | case charset.ShiftJIS: 33 | fallthrough 34 | case charset.UTF_8: 35 | eofLen = 1 36 | charLen = 1 37 | case charset.Unicode: 38 | fallthrough 39 | default: 40 | eofLen = 2 41 | charLen = 2 42 | } 43 | 44 | if slen == 0 { 45 | switch coding { 46 | case charset.ShiftJIS: 47 | fallthrough 48 | case charset.UTF_8: 49 | for end < len(bytes) && !(bytes[end] == 0) { 50 | end += charLen 51 | } 52 | case charset.Unicode: 53 | fallthrough 54 | default: 55 | for end+1 < len(bytes) && !(bytes[end] == 0 && bytes[end+1] == 0) { 56 | end += charLen 57 | } 58 | } 59 | } else { 60 | end = start + slen 61 | } 62 | 63 | str, _ := charset.ToUTF8(coding, bytes[start:end]) 64 | return str, end + eofLen 65 | } 66 | 67 | func ToUint8(data byte) uint8 { 68 | return uint8(data) 69 | } 70 | 71 | func ToUint16(data []byte) uint16 { 72 | return binary.LittleEndian.Uint16(data) 73 | } 74 | func ToUint32(data []byte) uint32 { 75 | return binary.LittleEndian.Uint32(data) 76 | } 77 | 78 | func ToString(format string, a ...interface{}) string { 79 | return fmt.Sprintf(format, a...) 80 | } 81 | 82 | // AllToUint16 将数据转为uint16列表,若正好转化完则返回0,否则返回最后一位位置 83 | func AllToUint16(data []byte) (list []uint16, end int) { 84 | dataLen := len(data) 85 | if dataLen%2 == 0 { 86 | end = -1 87 | } else { 88 | end = dataLen - 1 89 | } 90 | list = make([]uint16, 0, dataLen/2) 91 | for i := 0; i < (dataLen & ^1); i += 2 { 92 | list = append(list, binary.LittleEndian.Uint16(data[i:i+2])) 93 | } 94 | return list, end 95 | } 96 | 97 | // SetParam 参数转为字节 98 | // 99 | // 1.codeBytes 完整的参数字节数据 100 | // 2.data[0] Paramter类型指针 101 | // 3.data[1] start 可空,默认0。当前参数开始位置 102 | // 4.data[2] size 可空,默认对于Paramter类型长度。当前参数字节长度 103 | // 5.data[3] coding 可空,默认Unicode。LString类型编码 104 | // return start+size,即下个参数的start 105 | func GetParam(codeBytes []byte, data ...interface{}) int { 106 | var start, size int 107 | var coding charset.Charset 108 | if len(data) >= 2 { 109 | start = data[1].(int) 110 | } else { 111 | start = 0 112 | } 113 | 114 | if len(data) >= 3 { 115 | size = data[2].(int) 116 | } else { 117 | size = 0 118 | } 119 | 120 | if len(data) >= 4 { 121 | coding = data[3].(charset.Charset) 122 | } else { 123 | coding = charset.Unicode 124 | } 125 | 126 | switch value := data[0].(type) { 127 | case *uint8: 128 | *value = codeBytes[start] 129 | return start + 1 130 | case *uint16: 131 | if size == 0 { 132 | size = 2 133 | } 134 | *value = ToUint16(codeBytes[start : start+size]) 135 | return start + size 136 | case *uint32: 137 | if size == 0 { 138 | size = 4 139 | } 140 | *value = ToUint32(codeBytes[start : start+size]) 141 | return start + size 142 | case *string: 143 | tmp, next := DecodeString(codeBytes, start, size, coding) 144 | *value = tmp 145 | return next 146 | case *lstring: 147 | l := int(ToUint16(codeBytes[start : start+2])) 148 | switch coding { 149 | case charset.Unicode, charset.ShiftJIS: 150 | size = l * 2 151 | case charset.UTF_8: 152 | size = 0x10000 - l 153 | } 154 | if l == 0 { 155 | size = 0 156 | } 157 | tmp, next := DecodeString(codeBytes, start+2, size, coding) 158 | *value = lstring(tmp) 159 | return next 160 | default: 161 | return start 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /game/runtime/context.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "lucksystem/charset" 9 | "lucksystem/game/engine" 10 | "lucksystem/game/enum" 11 | "lucksystem/script" 12 | ) 13 | 14 | type Runtime struct { 15 | Script *script.Script 16 | 17 | *GlobalGoto 18 | 19 | OpcodeMap map[uint8]string 20 | ExprCharset charset.Charset 21 | TextCharset charset.Charset 22 | DefaultExport bool 23 | 24 | // 引擎前端 25 | Engine *engine.Engine 26 | 27 | // 当前下标 28 | CIndex int 29 | 30 | // 等待阻塞 31 | ChanEIP chan int 32 | 33 | // 运行模式 34 | RunMode enum.VMRunMode 35 | } 36 | 37 | func NewRuntime(mode enum.VMRunMode) *Runtime { 38 | ctx := &Runtime{ 39 | GlobalGoto: NewGlobalGoto(), 40 | Engine: &engine.Engine{}, 41 | ChanEIP: make(chan int), 42 | RunMode: mode, 43 | } 44 | return ctx 45 | } 46 | 47 | func (ctx *Runtime) SwitchScript(scr *script.Script) { 48 | ctx.Script = scr 49 | } 50 | 51 | func (ctx *Runtime) Init(exprCharset, textCharset charset.Charset, defaultExport bool) { 52 | ctx.ExprCharset = exprCharset 53 | ctx.TextCharset = textCharset 54 | ctx.DefaultExport = defaultExport 55 | } 56 | 57 | func (ctx *Runtime) LoadOpcode(path string) { 58 | data, err := os.ReadFile(path) 59 | if err != nil { 60 | panic(err) 61 | } 62 | if data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF { 63 | data = data[3:] 64 | } 65 | strlines := strings.Split(string(data), "\n") 66 | ctx.OpcodeMap = make(map[uint8]string, len(strlines)+1) 67 | for i, line := range strlines { 68 | line = strings.Replace(line, "\r", "", -1) 69 | line = strings.Replace(line, "\n", "", -1) 70 | line = strings.Replace(line, " ", "", -1) 71 | ctx.OpcodeMap[uint8(i)] = line 72 | } 73 | } 74 | 75 | func (ctx *Runtime) Opcode(index uint8) string { 76 | if op, ok := ctx.OpcodeMap[index]; ok { 77 | return op 78 | } 79 | return fmt.Sprintf("0x%02X", index) 80 | } 81 | 82 | // Code 获取当前code 83 | func (ctx *Runtime) Code() *script.CodeLine { 84 | return ctx.Script.Codes[ctx.CIndex] 85 | } 86 | -------------------------------------------------------------------------------- /game/runtime/global_goto.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/golang/glog" 7 | "lucksystem/script" 8 | ) 9 | 10 | type GlobalGoto struct { 11 | ScriptNames map[string]struct{} 12 | 13 | // 当前全局标签序号 14 | CLabelIndexNext int 15 | // 标签序号 -> 目标文件,目标位置 16 | GlobalLabelGoto map[int]*script.GlobalLabel 17 | // 标签序号 -> 目标位置。导入用 18 | IGlobalLabelMap map[int]int 19 | 20 | // 目标文件 -> 目标位置 -> 标签序号 21 | GlobalLabelMap map[string]map[int]int 22 | // 目标文件 -> 代码序号 -> 标签序号 23 | GlobalGotoMap map[string]map[int]int 24 | } 25 | 26 | func NewGlobalGoto() *GlobalGoto { 27 | return &GlobalGoto{ 28 | ScriptNames: make(map[string]struct{}), 29 | GlobalLabelGoto: make(map[int]*script.GlobalLabel), 30 | IGlobalLabelMap: make(map[int]int), 31 | CLabelIndexNext: 1, 32 | GlobalLabelMap: make(map[string]map[int]int), 33 | GlobalGotoMap: make(map[string]map[int]int), 34 | } 35 | } 36 | 37 | func (g *GlobalGoto) AddLabel(scriptName string, codeIndex, position int) (index int) { 38 | if _, ok := g.ScriptNames[scriptName]; !ok { 39 | scriptName = strings.ToUpper(scriptName) 40 | if _, ok := g.ScriptNames[scriptName]; !ok { 41 | scriptName = strings.ToLower(scriptName) 42 | } 43 | } 44 | 45 | index = g.CLabelIndexNext 46 | if _, ok := g.GlobalLabelMap[scriptName]; !ok { 47 | g.GlobalLabelMap[scriptName] = make(map[int]int) 48 | } 49 | if _, ok := g.GlobalGotoMap[scriptName]; !ok { 50 | g.GlobalGotoMap[scriptName] = make(map[int]int) 51 | } 52 | 53 | if i, ok := g.GlobalLabelMap[scriptName][position]; ok { 54 | return i 55 | } 56 | 57 | g.GlobalLabelMap[scriptName][position] = index 58 | g.GlobalGotoMap[scriptName][codeIndex] = index 59 | 60 | g.GlobalLabelGoto[index] = &script.GlobalLabel{ 61 | ScriptName: scriptName, 62 | Index: index, 63 | Position: position, 64 | } 65 | g.CLabelIndexNext++ 66 | return index 67 | } 68 | 69 | func (g *GlobalGoto) GetMaps(scriptName string) (labels map[int]int, gotos map[int]int) { 70 | return g.GlobalLabelMap[scriptName], g.GlobalGotoMap[scriptName] 71 | } 72 | 73 | func (g *GlobalGoto) AddGlobalLabelMap(labels map[int]int) { 74 | for index, pos := range labels { 75 | if _, ok := g.GlobalLabelGoto[index]; !ok { 76 | glog.Warningf("全局标签不存在:global%s", index) 77 | continue 78 | } 79 | if _, ok := g.IGlobalLabelMap[index]; ok { 80 | glog.Warningf("重复标签定义:global%s", index) 81 | continue 82 | } 83 | g.IGlobalLabelMap[index] = pos 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /game/util.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | func ScriptCanLoad(name string) bool { 9 | for _, n := range ScriptBlackList { 10 | if strings.ToUpper(name) == strings.ToUpper(n) { 11 | return false 12 | } 13 | } 14 | return true 15 | } 16 | 17 | func IsExistDir(filepath string) (exist bool, dir bool) { 18 | s, err := os.Stat(filepath) 19 | exist = true 20 | if err != nil { 21 | exist = os.IsExist(err) 22 | } 23 | if exist { 24 | dir = s.IsDir() 25 | } 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module lucksystem 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/go-python/gpython v0.2.0 // indirect 8 | github.com/go-restruct/restruct v1.2.0-alpha 9 | github.com/golang/glog v1.0.0 10 | github.com/pkg/errors v0.9.1 // indirect 11 | github.com/spf13/cobra v1.5.0 12 | github.com/stretchr/testify v1.7.1 // indirect 13 | golang.org/x/image v0.1.0 14 | golang.org/x/text v0.4.0 15 | gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/go-python/gpython v0.2.0 h1:MW7m7pFnbpzHL88vhAdIhT1pgG1QUZ0Q5jcF94z5MBI= 6 | github.com/go-python/gpython v0.2.0/go.mod h1:fUN4z1X+GFaOwPOoHOAM8MOPnh1NJatWo/cDqGlZDEI= 7 | github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= 8 | github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= 9 | github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= 10 | github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= 11 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 12 | github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 13 | github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= 14 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 15 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 16 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 17 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 18 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 19 | github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= 20 | github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= 21 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 23 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 26 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 27 | github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= 28 | github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 29 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 30 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 31 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 32 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 33 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 35 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 36 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 37 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 41 | golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= 42 | golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= 43 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 44 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 45 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 46 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 47 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= 56 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 58 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 60 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 61 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 62 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 63 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 64 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 65 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 66 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 67 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 69 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 70 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 71 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 72 | gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 h1:dbuHpmKjkDzSOMKAWl10QNlgaZUd3V1q99xc81tt2Kc= 73 | gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 74 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 WeTor wetorx@qq.com 3 | 4 | */ 5 | package main 6 | 7 | import ( 8 | "lucksystem/cmd" 9 | ) 10 | 11 | func main() { 12 | cmd.Execute() 13 | } 14 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "testing" 9 | 10 | "lucksystem/charset" 11 | "lucksystem/game" 12 | "lucksystem/game/VM" 13 | "lucksystem/game/enum" 14 | "lucksystem/script" 15 | 16 | "github.com/go-restruct/restruct" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | flag.Set("alsologtostderr", "true") 21 | flag.Set("log_dir", "log") 22 | flag.Set("v", "5") 23 | flag.Parse() 24 | 25 | ret := m.Run() 26 | os.Exit(ret) 27 | } 28 | func Test11(t *testing.T) { 29 | n, err := strconv.Atoi("123a") 30 | fmt.Println(n, err) 31 | } 32 | 33 | type lenString string 34 | 35 | func Test22(t *testing.T) { 36 | var val1 lenString = "test" 37 | val2 := "test2" 38 | iface := []interface{}{val1, val2} 39 | for _, i := range iface { 40 | switch val := i.(type) { 41 | case string: 42 | fmt.Println("string", val) 43 | case lenString: 44 | fmt.Println("lenString", val) 45 | } 46 | } 47 | } 48 | func TestLB_EN(t *testing.T) { 49 | restruct.EnableExprBeta() 50 | 51 | script := script.LoadScript(&script.LoadOptions{ 52 | Filename: "data/LB_EN/SCRIPT/SEEN2005", 53 | }) 54 | 55 | vm := VM.NewVM(&VM.Options{ 56 | GameName: "LB_EN", 57 | Mode: enum.VMRunExport, 58 | }) 59 | vm.LoadScript(script, true) 60 | vm.LoadOpcode("data/LB_EN/OPCODE.txt") 61 | 62 | vm.Run() 63 | f, _ := os.Create("data/LB_EN/TXT/SEEN2005.txt") 64 | defer f.Close() 65 | script.Export(f) 66 | 67 | } 68 | 69 | func TestLoadLB_EN(t *testing.T) { 70 | restruct.EnableExprBeta() 71 | script := script.LoadScript(&script.LoadOptions{ 72 | Filename: "data/LB_EN/SCRIPT/SEEN2005", 73 | }) 74 | 75 | f, _ := os.Open("data/LB_EN/TXT/SEEN2005.txt") 76 | defer f.Close() 77 | err := script.Import(f) 78 | if err != nil { 79 | fmt.Println(err) 80 | } 81 | 82 | vm := VM.NewVM(&VM.Options{ 83 | GameName: "LB_EN", 84 | Mode: enum.VMRunImport, 85 | }) 86 | vm.LoadScript(script, true) 87 | 88 | vm.LoadOpcode("data/LB_EN/OPCODE.txt") 89 | 90 | vm.Run() 91 | sf, _ := os.Create(script.FileName + ".out") 92 | defer sf.Close() 93 | err = script.Write(sf) 94 | if err != nil { 95 | fmt.Println(err) 96 | } 97 | } 98 | 99 | func TestSP(t *testing.T) { 100 | restruct.EnableExprBeta() 101 | 102 | script := script.LoadScript(&script.LoadOptions{ 103 | Filename: "C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/10_日常0729", 104 | }) 105 | 106 | // entry, err := pak.Get("10_日常0730") 107 | // if err != nil { 108 | // fmt.Println(err) 109 | // panic(err) 110 | // } 111 | // script.ReadByEntry(entry) 112 | vm := VM.NewVM(&VM.Options{ 113 | GameName: "SP", 114 | Mode: enum.VMRunExport, 115 | }) 116 | vm.LoadScript(script, true) 117 | vm.LoadOpcode("data/SP/OPCODE.txt") 118 | // game := game.NewGame("SP") 119 | // err := game.LoadOpcode("data/SP/OPCODE.txt") 120 | 121 | vm.Run() 122 | //fmt.Println(vm.Runtime.Variable.ValueMap) 123 | f, _ := os.Create("C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/10_日常0729.txt") 124 | defer f.Close() 125 | script.Export(f) 126 | } 127 | 128 | func TestPlugin(t *testing.T) { 129 | restruct.EnableExprBeta() 130 | script := script.LoadScript(&script.LoadOptions{ 131 | Filename: "C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/_称号_CS用処理", 132 | }) 133 | 134 | vm := VM.NewVM(&VM.Options{ 135 | GameName: "SP", 136 | Mode: enum.VMRunExport, 137 | PluginFile: "data/SP.py", 138 | }) 139 | vm.LoadScript(script, true) 140 | vm.LoadOpcode("data/SP/OPCODE.txt") 141 | 142 | vm.Run() 143 | for i := 1; i < len(vm.GlobalLabelGoto)+1; i++ { 144 | fmt.Println(i, " ", vm.GlobalLabelGoto[i]) 145 | } 146 | 147 | f, _ := os.Create("C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/_称号_CS用処理.txt") 148 | defer f.Close() 149 | script.Export(f) 150 | } 151 | 152 | func TestPluginImport(t *testing.T) { 153 | restruct.EnableExprBeta() 154 | var err error 155 | script := script.LoadScript(&script.LoadOptions{ 156 | Filename: "C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/10_日常0729", 157 | }) 158 | 159 | f, _ := os.Open("C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/10_日常0729.txt") 160 | defer f.Close() 161 | err = script.Import(f) 162 | if err != nil { 163 | fmt.Println(err) 164 | } 165 | 166 | vm := VM.NewVM(&VM.Options{ 167 | GameName: "SP", 168 | Mode: enum.VMRunImport, 169 | PluginFile: "data/SP.py", 170 | }) 171 | vm.LoadScript(script, true) 172 | vm.LoadOpcode("data/SP/OPCODE.txt") 173 | 174 | vm.Run() 175 | sf, _ := os.Create(script.FileName + ".out") 176 | defer sf.Close() 177 | err = script.Write(sf) 178 | if err != nil { 179 | fmt.Println(err) 180 | } 181 | } 182 | 183 | func TestLoadSP(t *testing.T) { 184 | restruct.EnableExprBeta() 185 | script := script.LoadScript(&script.LoadOptions{ 186 | Filename: "C:/Users/wetor/Desktop/Prototype/SCRIPT.PAK_unpacked/TXT/10_日常0729", 187 | }) 188 | 189 | f, _ := os.Open("data/SP/TXT/10_日常0729.txt") 190 | defer f.Close() 191 | err := script.Import(f) 192 | if err != nil { 193 | fmt.Println(err) 194 | } 195 | 196 | vm := VM.NewVM(&VM.Options{ 197 | GameName: "SP", 198 | Mode: enum.VMRunImport, 199 | }) 200 | vm.LoadScript(script, true) 201 | vm.LoadOpcode("data/SP/OPCODE.txt") 202 | 203 | vm.Run() 204 | sf, _ := os.Create(script.FileName + ".out") 205 | defer sf.Close() 206 | err = script.Write(sf) 207 | if err != nil { 208 | fmt.Println(err) 209 | } 210 | } 211 | 212 | func TestLoopersExportScript(t *testing.T) { 213 | // 反编译LOOPERS SCRIPT.PAK 214 | restruct.EnableExprBeta() 215 | g := game.NewGame(&game.GameOptions{ 216 | GameName: "LOOPERS", 217 | PluginFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/LOOPERS.py", 218 | OpcodeFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/LOOPERS.txt", 219 | Coding: charset.UTF_8, 220 | Mode: enum.VMRunExport, 221 | }) 222 | g.LoadScriptResources("D:\\Game\\LOOPERS\\LOOPERS\\files\\src\\SCRIPT.PAK") 223 | g.RunScript() 224 | 225 | g.ExportScript("D:\\Game\\LOOPERS\\LOOPERS\\files\\Export", false) 226 | 227 | } 228 | 229 | func TestLoopersImportScript(t *testing.T) { 230 | // LOOPERS修改后导入 SCRIPT.PAK 231 | restruct.EnableExprBeta() 232 | g := game.NewGame(&game.GameOptions{ 233 | GameName: "LOOPERS", 234 | PluginFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/LOOPERS.py", 235 | OpcodeFile: "C:/Users/wetor/GolandProjects/LuckSystem/data/LOOPERS.txt", 236 | Coding: charset.UTF_8, 237 | Mode: enum.VMRunImport, 238 | }) 239 | g.LoadScriptResources("D:\\Game\\LOOPERS\\LOOPERS\\files\\src\\SCRIPT.PAK") 240 | g.ImportScript("D:\\Game\\LOOPERS\\LOOPERS\\files\\Export", false) 241 | g.RunScript() 242 | 243 | g.ImportScriptWrite("D:\\Game\\LOOPERS\\LOOPERS\\files\\Import\\SCRIPT.PAK") 244 | 245 | os.Rename("D:\\Game\\LOOPERS\\LOOPERS\\files\\Import\\SCRIPT.PAK", "D:\\Game\\LOOPERS\\LOOPERS\\files\\SCRIPT.PAK") 246 | } 247 | -------------------------------------------------------------------------------- /script/entry.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | // Entry 脚本导入导出实体 4 | // 不可用作运行时,需先转话为Script 5 | type Entry struct { 6 | // 递增序列,从1开始 7 | IndexNext int 8 | 9 | // 导出: 10 | // 1.执行脚本,遇到GOTO等跳转指令,将jumpPos作为key,递增序列Index作为value,存入ELabelMap,即一个地址只能存在一个LabelIndex 11 | // 2.同时,将当前指令的CodeIndex作为key,1中和jumpPos对应的LabelIndex作为value,存入EGotoMap,即标记次条语句包含跳转到LabelIndex的指令 12 | ELabelMap map[int]int // Pos(跳转地址) -> LabelIndex(标签序号) ,Pos 通过GOTO (pos) 生成,Index 为序列 13 | EGotoMap map[int]int // CodeIndex(代码序号) -> LabelIndex(标签序号) ,CodeIndex为当前语句序列,此语句含有跳转指令,跳转到LabelIndex 14 | EGlobalLabelMap map[int]int // Pos(跳转地址) -> LabelIndex(标签序号) 15 | EGlobalGotoMap map[int]int // CodeIndex(代码序号) -> LabelIndex(标签序号) 16 | // 导入当前位置 17 | CurPos int 18 | // 导入: 19 | // 1.解析文本,同时开始序列化脚本,转为二进制数据并写入。 20 | // 2.遇到Label标签,将LabelIndex作为key,当前语句开始位置的文件偏移Pos作为value,存入ILabelMap,即标签对应的跳转地址 21 | // 3.遇到GOTO等跳转指令时,将要跳转到的LabelIndex作为key,[jumpPos参数所在的文件偏移]作为value存入IGotoMap,即暂时留空,后续再补充数据 22 | // 4.数据写入完成,遍历IGotoMap,根据ILabelMap的key,即LabelIndex,在ILabelMap中取得语句偏移Pos,写入[jumpPos参数所在的文件偏移]位置,填充数据。 23 | ILabelMap map[int]int // LabelIndex(标签序号) -> CodeStartPos(代码开头地址,跳转目标地址) 24 | IGotoMap map[int]int // GotoParamPos(跳转参数地址) -> LabelIndex(标签序号) 25 | 26 | IGlobalLabelMap map[int]int // LabelIndex(标签序号) -> CodeStartPos(代码开头地址,跳转目标地址),需要统合所有script的标签 27 | IGlobalGotoMap map[int]int // GotoParamPos(跳转参数地址) -> LabelIndex(标签序号) 28 | } 29 | 30 | func (e *Entry) InitEntry() { 31 | e.ELabelMap = make(map[int]int) 32 | e.EGotoMap = make(map[int]int) 33 | e.EGlobalLabelMap = make(map[int]int) 34 | e.EGlobalGotoMap = make(map[int]int) 35 | 36 | e.ILabelMap = make(map[int]int) 37 | e.IGotoMap = make(map[int]int) 38 | e.IGlobalLabelMap = make(map[int]int) 39 | e.IGlobalGotoMap = make(map[int]int) 40 | 41 | e.IndexNext = 1 42 | e.CurPos = 0 43 | } 44 | 45 | func (e *Entry) AddExportGotoLabel(codeIndex, pos int) int { 46 | 47 | val, has := e.ELabelMap[pos] 48 | if has { 49 | e.EGotoMap[codeIndex] = val 50 | return val 51 | } 52 | e.ELabelMap[pos] = e.IndexNext 53 | e.EGotoMap[codeIndex] = e.IndexNext 54 | e.IndexNext++ 55 | return e.ELabelMap[pos] 56 | } 57 | 58 | // labelIndex, Goto参数位置 59 | func (e *Entry) AddImportGoto(pos, labelIndex int) { 60 | e.IGotoMap[pos] = labelIndex 61 | } 62 | 63 | // labelIndex, 当前代码位置 64 | func (e *Entry) AddImportLabel(labelIndex, pos int) { 65 | e.ILabelMap[labelIndex] = pos 66 | } 67 | 68 | func (e *Entry) SetGlobalLabel(labels map[int]int) { 69 | e.EGlobalLabelMap = labels 70 | } 71 | 72 | func (e *Entry) SetGlobalGoto(gotos map[int]int) { 73 | e.EGlobalGotoMap = gotos 74 | } 75 | 76 | func (e *Entry) AddImportGlobalGoto(pos, labelIndex int) { 77 | e.IGlobalGotoMap[pos] = labelIndex 78 | } 79 | 80 | func (e *Entry) AddImportGlobalLabel(labelIndex, pos int) { 81 | e.IGlobalLabelMap[labelIndex] = pos 82 | } 83 | 84 | func (e *Entry) SetImportGlobalLabel(labels map[int]int) { 85 | e.IGlobalLabelMap = labels 86 | } 87 | -------------------------------------------------------------------------------- /script/model.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "lucksystem/charset" 5 | "lucksystem/pak" 6 | ) 7 | 8 | type GlobalLabel struct { 9 | ScriptName string 10 | Index int // 序号,从1开始 11 | Position int 12 | } 13 | 14 | type JumpParam struct { 15 | GlobalIndex int 16 | ScriptName string 17 | Position int 18 | } 19 | 20 | type StringParam struct { 21 | Data string 22 | Coding charset.Charset 23 | HasLen bool 24 | } 25 | 26 | type Info struct { 27 | FileName string 28 | Name string 29 | CodeNum int 30 | } 31 | 32 | type CodeInfo struct { 33 | Index int // 序号 34 | Pos int // 文件偏移,和Index绑定 35 | LabelIndex int // 跳转目标标记,从1开始 36 | GotoIndex int // 跳转标记,为0则不使用 37 | 38 | GlobalLabelIndex int // 全局跳转目标标记,从1开始 39 | GlobalGotoIndex int // 全局跳转标记,为0则不使用 40 | } 41 | 42 | type LoadOptions struct { 43 | Filename string 44 | Entry *pak.Entry 45 | 46 | Name string // optional 47 | } 48 | -------------------------------------------------------------------------------- /script/util.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func ToStringCodeParams(code *CodeLine) string { 10 | paramStr := make([]string, 0, len(code.Params)) 11 | for i := 0; i < len(code.Params); i++ { 12 | switch param := code.Params[i].(type) { 13 | case []uint16: 14 | for _, val := range param { 15 | paramStr = append(paramStr, strconv.FormatInt(int64(val), 10)) 16 | } 17 | case byte: 18 | paramStr = append(paramStr, fmt.Sprintf("0x%X", param)) 19 | case string: 20 | paramStr = append(paramStr, `"`+param+`"`) 21 | case *JumpParam: 22 | if code.GotoIndex > 0 { 23 | paramStr = append(paramStr, fmt.Sprintf("{goto label%d}", code.GotoIndex)) 24 | } else if param.GlobalIndex > 0 { 25 | paramStr = append(paramStr, fmt.Sprintf(`{goto "%s" global%d}`, param.ScriptName, param.GlobalIndex)) 26 | } else { 27 | paramStr = append(paramStr, fmt.Sprintf("{goto %d}", param.Position)) 28 | } 29 | default: 30 | paramStr = append(paramStr, fmt.Sprintf("%v", param)) 31 | 32 | } 33 | } 34 | str := strings.Join(paramStr, ", ") 35 | str = fmt.Sprintf(`%s (%s)`, code.OpStr, str) 36 | if code.LabelIndex > 0 { 37 | str = fmt.Sprintf(`label%d: %s`, code.LabelIndex, str) 38 | } 39 | if code.GlobalLabelIndex > 0 { 40 | str = fmt.Sprintf(`global%d: %s`, code.GlobalLabelIndex, str) 41 | } 42 | return str 43 | } 44 | 45 | func ParseCodeParams(code *CodeLine, codeStr string) { 46 | word := make([]rune, 0, 32) 47 | params := make([]interface{}, 0, 8) 48 | opStr := "" 49 | labelIndex := 0 50 | gotoIndex := 0 51 | globalLabelIndex := 0 52 | globalGotoIndex := 0 53 | gotoFile := "" 54 | isString := false 55 | isSpecial := false 56 | 57 | for _, ch := range codeStr { 58 | if isString { 59 | if ch == '"' { 60 | if len(word) == 0 { // 空字符串 61 | word = append(word, '\x00') 62 | } 63 | isString = false 64 | continue 65 | } 66 | word = append(word, ch) 67 | continue 68 | } 69 | 70 | switch ch { 71 | case ' ', ',', '(', ')', '}', '\n': 72 | if len(word) > 0 { 73 | wordStr := string(word) 74 | if word[0] == '\x00' { 75 | wordStr = "" 76 | } 77 | if opStr == "" { 78 | opStr = wordStr 79 | } else if isSpecial { 80 | if len(word) > 5 && wordStr[0:5] == "label" { 81 | gotoIndex, _ = strconv.Atoi(wordStr[5:]) 82 | } else if len(word) > 6 && wordStr[0:6] == "global" { 83 | globalGotoIndex, _ = strconv.Atoi(wordStr[6:]) 84 | } else if wordStr != "goto" { 85 | gotoFile = wordStr 86 | } 87 | } else { 88 | params = append(params, wordStr) 89 | } 90 | word = word[0:0] 91 | } 92 | if ch == '}' { 93 | isSpecial = false 94 | } 95 | case ':': 96 | if len(word) > 5 && string(word[0:5]) == "label" { 97 | labelIndex, _ = strconv.Atoi(string(word[5:])) 98 | } else if len(word) > 6 && string(word[0:6]) == "global" { 99 | globalLabelIndex, _ = strconv.Atoi(string(word[6:])) 100 | } 101 | word = word[0:0] 102 | case '"': 103 | isString = true 104 | case '{': 105 | isSpecial = true 106 | default: 107 | word = append(word, ch) 108 | } 109 | } 110 | code.OpStr = opStr 111 | code.LabelIndex = labelIndex 112 | code.GotoIndex = gotoIndex 113 | 114 | code.GlobalGotoIndex = globalGotoIndex 115 | code.GlobalLabelIndex = globalLabelIndex 116 | 117 | if gotoFile != "" || gotoIndex > 0 || globalGotoIndex > 0 { 118 | params = append(params, &JumpParam{ 119 | GlobalIndex: globalGotoIndex, 120 | ScriptName: gotoFile, 121 | Position: gotoIndex + globalGotoIndex, // 填充使用 122 | }) 123 | } 124 | code.Params = params 125 | 126 | // if labelIndex > 0 { 127 | // fmt.Printf("label%d: ", labelIndex) 128 | // } 129 | // fmt.Printf("%s %v", opStr, params) 130 | // if gotoIndex > 0 { 131 | // fmt.Printf(" {goto label%d}", gotoIndex) 132 | // } 133 | // fmt.Print("\n") 134 | // _, _, _ = labelIndex, gotoIndex, params 135 | } 136 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func GetDirFileList(dir string) ([]string, error) { 9 | var files []string 10 | //方法一 11 | var walkFunc = func(path string, info os.FileInfo, err error) error { 12 | if !info.IsDir() { 13 | files = append(files, path) 14 | } 15 | //fmt.Printf("%s\n", path) 16 | return nil 17 | } 18 | err := filepath.Walk(dir, walkFunc) 19 | return files, err 20 | } 21 | -------------------------------------------------------------------------------- /voice/oggpak.go: -------------------------------------------------------------------------------- 1 | package voice 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/go-restruct/restruct" 6 | "github.com/golang/glog" 7 | ) 8 | 9 | type OggPak struct { 10 | Magic []byte `struct:"size=7"` 11 | Files []*OggFile `struct:"while=!_eof"` 12 | Index int `struct:"-"` 13 | } 14 | type OggFile struct { 15 | SampleRate uint32 16 | Length uint32 17 | Data []byte `struct:"size=Length"` 18 | } 19 | 20 | func LoadOggPak(index int, data []byte) (*OggPak, error) { 21 | oggPak := &OggPak{} 22 | err := restruct.Unpack(data, binary.LittleEndian, oggPak) 23 | if err != nil { 24 | glog.V(8).Infoln("restruct.Unpack", err.Error()) 25 | return nil, err 26 | } 27 | oggPak.Index = index 28 | return oggPak, nil 29 | } 30 | --------------------------------------------------------------------------------