├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "adler32" 3 | version = "1.0.3" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "ansi_term" 8 | version = "0.11.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.11" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "autocfg" 26 | version = "0.1.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "bincode" 31 | version = "1.1.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "bitflags" 41 | version = "1.0.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "build_const" 46 | version = "0.2.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "byteorder" 51 | version = "1.3.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "cc" 56 | version = "1.0.31" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "0.1.7" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | 64 | [[package]] 65 | name = "clap" 66 | version = "2.32.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "crc" 80 | version = "1.8.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 84 | ] 85 | 86 | [[package]] 87 | name = "crc32fast" 88 | version = "1.2.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | dependencies = [ 91 | "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "ctce8_cfg_tool" 96 | version = "0.1.0" 97 | dependencies = [ 98 | "bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "flate2" 107 | version = "1.0.7" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "libc" 118 | version = "0.2.50" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | 121 | [[package]] 122 | name = "libz-sys" 123 | version = "1.0.25" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "miniz_oxide" 134 | version = "0.2.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | dependencies = [ 137 | "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "miniz_oxide_c_api" 142 | version = "0.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | dependencies = [ 145 | "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "pkg-config" 153 | version = "0.3.14" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "proc-macro2" 158 | version = "0.4.27" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 162 | ] 163 | 164 | [[package]] 165 | name = "quote" 166 | version = "0.6.11" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | dependencies = [ 169 | "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "redox_syscall" 174 | version = "0.1.51" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | 177 | [[package]] 178 | name = "redox_termios" 179 | version = "0.1.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | dependencies = [ 182 | "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "serde" 187 | version = "1.0.89" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | dependencies = [ 190 | "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", 191 | ] 192 | 193 | [[package]] 194 | name = "serde_derive" 195 | version = "1.0.89" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | dependencies = [ 198 | "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", 201 | ] 202 | 203 | [[package]] 204 | name = "strsim" 205 | version = "0.7.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | 208 | [[package]] 209 | name = "syn" 210 | version = "0.15.29" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | dependencies = [ 213 | "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "termion" 220 | version = "1.5.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | dependencies = [ 223 | "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", 224 | "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 226 | ] 227 | 228 | [[package]] 229 | name = "textwrap" 230 | version = "0.10.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | dependencies = [ 233 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 234 | ] 235 | 236 | [[package]] 237 | name = "unicode-width" 238 | version = "0.1.5" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | 241 | [[package]] 242 | name = "unicode-xid" 243 | version = "0.1.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | 246 | [[package]] 247 | name = "vcpkg" 248 | version = "0.2.6" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | 251 | [[package]] 252 | name = "vec_map" 253 | version = "0.8.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | 256 | [[package]] 257 | name = "winapi" 258 | version = "0.3.6" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | dependencies = [ 261 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 263 | ] 264 | 265 | [[package]] 266 | name = "winapi-i686-pc-windows-gnu" 267 | version = "0.4.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | 270 | [[package]] 271 | name = "winapi-x86_64-pc-windows-gnu" 272 | version = "0.4.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | 275 | [metadata] 276 | "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" 277 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 278 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 279 | "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" 280 | "checksum bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3efe0b4c8eaeed8600549c29f538a6a11bf422858d0ed435b1d70ec4ab101190" 281 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 282 | "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" 283 | "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" 284 | "checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" 285 | "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" 286 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 287 | "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" 288 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 289 | "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" 290 | "checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" 291 | "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" 292 | "checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e" 293 | "checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab" 294 | "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" 295 | "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" 296 | "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" 297 | "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" 298 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 299 | "checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" 300 | "checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" 301 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 302 | "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" 303 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 304 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 305 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 306 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 307 | "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" 308 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 309 | "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 310 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 311 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 312 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctce8_cfg_tool" 3 | version = "0.1.0" 4 | authors = ["spoon"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = { version = "1.0", features = ["derive"] } 9 | bincode = "1.1" 10 | crc = "1.8" 11 | flate2 = { version = "1.0", features = ["zlib"], default-features = false } 12 | clap = "2.32" 13 | 14 | [profile.release] 15 | panic = 'abort' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctce8_cfg_tool 2 | 3 | A tool for packing/unpacking ZTE Optical Modem configuration file 4 | 5 | 打包/解包中兴电信光猫 CTCE8 格式 cfg 配置文件工具 6 | 7 | 8 | ### 用法: 9 | 10 | ```PowerShell 11 | .\ctce8_cfg_tool.exe unpack "E:\e8_Config_Backup\ctce8_ZXHN_F450.cfg" ctce8_ZXHN_F450.xml 12 | .\ctce8_cfg_tool.exe pack ctce8_ZXHN_F450.xml ctce8_ZXHN_F450.cfg "ZXHN F450" 13 | ``` 14 | 15 | pack 打包命令的第三个参数是光猫设备名,一般为配置文件名中的字段。如我得到的配置文件名为 ctce8_ZXHN_F450.cfg,那么这个字符串就是 "ZXHN F450",注意中间是空格。 16 | 17 | 准确描述见源码 [main.rs#L354](https://github.com/AlfnXd/ctce8_cfg_tool/blob/master/src/main.rs#L354),Seek 跳过的部分就是这个字符串,可用 16 进制编辑器查看。 18 | 19 | ### 注意: 20 | 21 | - 操作有风险,请做好备份工作之后再继续。 22 | - 只在电信光猫 ZXHN F450 v2.0 上测试过。使用工具解包再打包回 cfg 文件,与光猫生成的 cfg 文件相同,修改后的重新打包的 cfg 文件也可以被光猫正确读取。 23 | - 建议使用此工具解包而不是选择 [offzip](http://aluigi.altervista.org/mytoolz/offzip.zip) 等工具,因为解包代码中有仔细的校验逻辑,不兼容的 cfg 文件会有提示。 24 | 25 | ### 感谢 26 | 27 | - [https://github.com/wx1183618058/ZET-Optical-Network-Terminal-Decoder](https://github.com/wx1183618058/ZET-Optical-Network-Terminal-Decoder) 28 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, File}; 2 | 3 | use std::io; 4 | use std::io::{BufReader, BufWriter}; 5 | use std::io::{Read, Write}; 6 | use std::io::{Seek, SeekFrom}; 7 | 8 | use std::mem; 9 | 10 | use std::error::{self, Error}; 11 | use std::fmt; 12 | 13 | use bincode::{self, config}; 14 | use crc::{crc32, Hasher32}; 15 | use serde::{Deserialize, Serialize}; 16 | 17 | // 错误统一处理 18 | macro_rules! turn { 19 | ($expr:expr) => { 20 | return core::result::Result::Err(core::convert::From::from($expr)); 21 | }; 22 | } 23 | 24 | #[derive(Debug)] 25 | enum CustomError<'a> { 26 | IoError(io::Error), 27 | UnpackError(UnpackError<'a>), 28 | DeserializeOrSerializeError(Box), 29 | DecompressError(flate2::DecompressError), 30 | CompressError(flate2::CompressError), 31 | } 32 | 33 | impl<'a> From for CustomError<'a> { 34 | fn from(err: io::Error) -> CustomError<'a> { 35 | CustomError::IoError(err) 36 | } 37 | } 38 | 39 | impl<'a> From> for CustomError<'a> { 40 | fn from(err: UnpackError<'a>) -> CustomError<'a> { 41 | CustomError::UnpackError(err) 42 | } 43 | } 44 | 45 | impl<'a> From> for CustomError<'a> { 46 | fn from(err: Box) -> CustomError<'a> { 47 | CustomError::DeserializeOrSerializeError(err) 48 | } 49 | } 50 | 51 | impl<'a> From for CustomError<'a> { 52 | fn from(err: flate2::DecompressError) -> CustomError<'a> { 53 | CustomError::DecompressError(err) 54 | } 55 | } 56 | 57 | impl<'a> From for CustomError<'a> { 58 | fn from(err: flate2::CompressError) -> CustomError<'a> { 59 | CustomError::CompressError(err) 60 | } 61 | } 62 | 63 | #[derive(Debug)] 64 | struct UnpackError<'a> { 65 | reason: &'a str, 66 | } 67 | 68 | impl<'a> fmt::Display for UnpackError<'a> { 69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 70 | write!(f, "{}", &self.reason) 71 | } 72 | } 73 | 74 | impl<'a> error::Error for UnpackError<'a> { 75 | fn description(&self) -> &str { 76 | &self.reason 77 | } 78 | 79 | fn source(&self) -> Option<&(dyn Error + 'static)> { 80 | None 81 | } 82 | } 83 | 84 | // ctce8 的一些文件数据格式定义 85 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 86 | struct CTCE8HeaderPart1 { 87 | flag1: [u32; 4], 88 | blank_bytes1: [u32; 2], 89 | flag2: u32, 90 | blank_bytes2: [u32; 8], 91 | flag3: u32, 92 | flag4: [u32; 2], 93 | } 94 | 95 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 96 | struct CTCE8HeaderPart2 { 97 | blank_bytes3: [u32; 13], 98 | flag5: [u32; 2], 99 | } 100 | 101 | #[derive(Serialize, Deserialize, Debug)] 102 | struct CfgHeader { 103 | flag1: [u32; 2], 104 | uncompressed_file_size: u32, 105 | output_data_with_header_size: u32, 106 | compress_chunk_size: u32, 107 | compressed_chunk_overlying_crc32: u32, 108 | cfg_header_crc32: u32, 109 | blank_bytes: [u32; 8], 110 | } 111 | 112 | #[derive(Serialize, Deserialize, Debug)] 113 | struct DataChunkHeader { 114 | before_compressed_size: u32, 115 | after_compressed_size: u32, 116 | chunk_end_offset: u32, 117 | } 118 | 119 | const READ_CHUNK_SIZE: usize = 0x10000; 120 | const MIN_ZLIB_COMPRESSED_DATA_SIZE: u32 = 9; // 以 level9 压缩一个字节,长度为9位。 其中 CMF(1) + FLG(1) + ADLER32(4) == 6。参见 https://tools.ietf.org/html/rfc1950 121 | 122 | static CTCE8_HEADER_PART1: CTCE8HeaderPart1 = CTCE8HeaderPart1 { 123 | flag1: [0x99999999, 0x44444444, 0x55555555, 0xAAAAAAAA], 124 | blank_bytes1: [0; 2], 125 | flag2: 0x04000000, 126 | blank_bytes2: [0; 8], 127 | flag3: 0x40000000, 128 | flag4: [0x02000000, 0x80000000], 129 | }; 130 | 131 | static CTCE8_HEADER_PART2: CTCE8HeaderPart2 = CTCE8HeaderPart2 { 132 | blank_bytes3: [0; 13], 133 | flag5: [0x04030201, 0], 134 | }; 135 | 136 | fn pack_to_cfg<'a>( 137 | xml_file_path: &str, 138 | ctce8_file_path: &str, 139 | device_model_string: &str, 140 | ) -> Result<(), CustomError<'a>> { 141 | use flate2::{Compress, Compression, FlushCompress}; 142 | 143 | let input_file_size = fs::metadata(&xml_file_path)?.len(); 144 | 145 | let input_file = File::open(&xml_file_path)?; 146 | let mut input_stream = BufReader::new(input_file); 147 | 148 | let output_file = File::create(&ctce8_file_path)?; 149 | let mut output_stream = BufWriter::new(output_file); 150 | 151 | let mut special_file_size: u32 = 0u32; // 最终输出 ctce8 文件大小 - 128,在 ZXHN F450 上是这样,其他地区机型未知 152 | let device_model_name_length: u32 = device_model_string.len() as u32; 153 | 154 | let header_placeholder_length = mem::size_of::() 155 | + mem::size_of_val(&special_file_size) 156 | + mem::size_of::() 157 | + mem::size_of_val(&device_model_name_length) 158 | + device_model_string.len() 159 | + mem::size_of::(); 160 | 161 | { 162 | let header_placeholder_bytes = vec![0u8; header_placeholder_length]; 163 | output_stream.write_all(&header_placeholder_bytes)?; 164 | } 165 | 166 | let mut big_endian_config = config(); 167 | big_endian_config.big_endian(); 168 | let mut little_endian_config = config(); 169 | little_endian_config.little_endian(); 170 | 171 | let data_chunk_header_size = mem::size_of::() as u32; 172 | 173 | let mut output_data_size = 0u32; // 文件头之后的数据区大小。数据区的每块数据由块头(data_chunk_header_size == 12 字节)加压缩数据组成 174 | let mut chunk_end_offset = mem::size_of::() as u32; // 注意是以 cfg header 开头作为起始地址,而不是文件初始位置 175 | let mut compressed_chunk_overlaying_crc32 = 0u32; // 压缩后的数据求 CRC32 值,以前一块压缩数据 CRC32 值为初始值,第一个初始值是 0 176 | 177 | { 178 | let mut compressed: Vec = 179 | Vec::with_capacity(READ_CHUNK_SIZE + MIN_ZLIB_COMPRESSED_DATA_SIZE as usize); 180 | let mut compressor = Compress::new(Compression::best(), true); 181 | 182 | let mut read_buffer: Vec = vec![0u8; READ_CHUNK_SIZE]; 183 | let mut read_chunk_size = READ_CHUNK_SIZE; 184 | 185 | let total_read_times: u64 = input_file_size / read_chunk_size as u64 + 1; 186 | for i in 0..total_read_times { 187 | if i == total_read_times - 1 { 188 | read_chunk_size = (input_file_size % read_chunk_size as u64) as usize; 189 | if read_chunk_size == 0 { 190 | break; 191 | } 192 | } 193 | 194 | input_stream.read_exact(&mut read_buffer[..read_chunk_size])?; 195 | 196 | compressor.compress_vec( 197 | &read_buffer[..read_chunk_size], 198 | &mut compressed, 199 | FlushCompress::Finish, 200 | )?; 201 | 202 | if read_chunk_size == READ_CHUNK_SIZE { 203 | chunk_end_offset += data_chunk_header_size + compressor.total_out() as u32; 204 | } else { 205 | chunk_end_offset = 0; // 以偏移值 0 标记接下来的数据块为最后一块 206 | } 207 | 208 | { 209 | let data_chunk_header = DataChunkHeader { 210 | before_compressed_size: compressor.total_in() as u32, 211 | after_compressed_size: compressor.total_out() as u32, 212 | chunk_end_offset: chunk_end_offset, 213 | }; 214 | 215 | let encoded: Vec = big_endian_config.serialize(&data_chunk_header)?; 216 | output_stream.write_all(&encoded)?; 217 | 218 | output_stream.write_all(&compressed)?; 219 | output_data_size += 220 | data_chunk_header_size + data_chunk_header.after_compressed_size; 221 | 222 | let mut crc32_digest = 223 | crc32::Digest::new_with_initial(crc32::IEEE, compressed_chunk_overlaying_crc32); 224 | crc32_digest.write(&compressed); 225 | compressed_chunk_overlaying_crc32 = crc32_digest.sum32(); 226 | } 227 | 228 | compressed.clear(); 229 | compressor.reset(); 230 | } 231 | } 232 | 233 | output_stream.seek(SeekFrom::Start(0))?; 234 | 235 | { 236 | let encoded: Vec = big_endian_config.serialize(&CTCE8_HEADER_PART1)?; 237 | output_stream.write_all(&encoded)?; 238 | } 239 | 240 | { 241 | special_file_size = output_data_size + header_placeholder_length as u32 - 128; 242 | let encoded: Vec = little_endian_config.serialize(&special_file_size)?; // 注意这里是用小端写入,也就这个地方特别 243 | output_stream.write_all(&encoded)?; 244 | } 245 | 246 | { 247 | let encoded: Vec = big_endian_config.serialize(&CTCE8_HEADER_PART2)?; 248 | output_stream.write_all(&encoded)?; 249 | } 250 | 251 | { 252 | let device_model_name_length: u32 = device_model_string.len() as u32; 253 | let encoded: Vec = big_endian_config.serialize(&device_model_name_length)?; 254 | output_stream.write_all(&encoded)?; 255 | 256 | output_stream 257 | .write_all(&(device_model_string.as_bytes()[..device_model_name_length as usize]))?; 258 | } 259 | 260 | { 261 | let mut cfg_header = CfgHeader { 262 | flag1: [0x01020304, 0], 263 | uncompressed_file_size: input_file_size as u32, 264 | output_data_with_header_size: output_data_size + mem::size_of::() as u32, 265 | compress_chunk_size: READ_CHUNK_SIZE as u32, 266 | compressed_chunk_overlying_crc32: compressed_chunk_overlaying_crc32, 267 | cfg_header_crc32: 0, 268 | blank_bytes: [0u32; 8], 269 | }; 270 | let encoded: Vec = big_endian_config.serialize(&cfg_header)?; 271 | 272 | let mut crc32_digest = crc32::Digest::new_with_initial(crc32::IEEE, 0u32); 273 | crc32_digest.write(&(encoded.as_slice()[..24])); 274 | cfg_header.cfg_header_crc32 = crc32_digest.sum32(); 275 | 276 | let encoded: Vec = big_endian_config.serialize(&cfg_header)?; 277 | output_stream.write_all(&encoded)?; 278 | } 279 | 280 | Ok(()) 281 | } 282 | 283 | fn unpack_to_xml<'a>(ctce8_file_path: &str, xml_file_path: &str) -> Result<(), CustomError<'a>> { 284 | use flate2::{Decompress, FlushDecompress}; 285 | 286 | let input_file_size = fs::metadata(&ctce8_file_path)?.len(); 287 | 288 | let input_file = File::open(&ctce8_file_path)?; 289 | let mut input_stream = BufReader::new(input_file); 290 | 291 | let output_file = File::create(&xml_file_path)?; 292 | let mut output_stream = BufWriter::new(output_file); 293 | 294 | let mut big_endian_config = config(); 295 | big_endian_config.big_endian(); 296 | let mut little_endian_config = config(); 297 | little_endian_config.little_endian(); 298 | 299 | { 300 | let size = mem::size_of::(); 301 | let mut read_buffer: Vec = vec![0u8; size]; 302 | input_stream.read_exact(&mut read_buffer)?; 303 | let decoded: CTCE8HeaderPart1 = big_endian_config.deserialize(&read_buffer)?; 304 | if decoded != CTCE8_HEADER_PART1 { 305 | turn!(UnpackError { 306 | reason: "文件格式不正确(CTCE8_HEADER_PART1)" 307 | }) 308 | } 309 | } 310 | 311 | let mut special_file_size: u32 = 0; 312 | { 313 | let mut read_buffer: Vec = vec![0u8; mem::size_of_val(&special_file_size)]; 314 | input_stream.read_exact(&mut read_buffer)?; 315 | special_file_size = little_endian_config.deserialize(&read_buffer)?; 316 | } 317 | 318 | if input_file_size - special_file_size as u64 != 128 { 319 | turn!(UnpackError { 320 | reason: "文件大小不正确,可能不兼容该版本 cfg 文件" 321 | }) 322 | } 323 | 324 | { 325 | let size = mem::size_of::(); 326 | let mut read_buffer: Vec = vec![0u8; size]; 327 | input_stream.read_exact(&mut read_buffer)?; 328 | let decoded: CTCE8HeaderPart2 = big_endian_config.deserialize(&read_buffer)?; 329 | if decoded != CTCE8_HEADER_PART2 { 330 | turn!(UnpackError { 331 | reason: "文件格式不正确(CTCE8_HEADER_PART2)" 332 | }) 333 | } 334 | } 335 | 336 | { 337 | let mut device_model_string_length: u32 = 0; 338 | let mut read_buffer: Vec = vec![0u8; mem::size_of_val(&device_model_string_length)]; 339 | input_stream.read_exact(&mut read_buffer)?; 340 | device_model_string_length = big_endian_config.deserialize(&read_buffer)?; 341 | 342 | let placeholder_length = mem::size_of::() 343 | + mem::size_of_val(&special_file_size) 344 | + mem::size_of::() 345 | + mem::size_of_val(&device_model_string_length) 346 | + device_model_string_length as usize 347 | + mem::size_of::(); 348 | if MIN_ZLIB_COMPRESSED_DATA_SIZE as u64 > input_file_size - placeholder_length as u64 { 349 | turn!(UnpackError { 350 | reason: "文件数据不正确,压缩的数据丢失" 351 | }) 352 | } 353 | 354 | input_stream.seek(SeekFrom::Current(device_model_string_length as i64))?; 355 | } 356 | 357 | let cfg_header: CfgHeader; 358 | { 359 | let mut read_buffer: Vec = vec![0u8; mem::size_of::()]; 360 | input_stream.read_exact(&mut read_buffer)?; 361 | cfg_header = big_endian_config.deserialize(&read_buffer)?; 362 | 363 | if cfg_header.flag1 != [0x01020304u32, 0u32] || cfg_header.blank_bytes != [0u32; 8] { 364 | turn!(UnpackError { 365 | reason: "文件格式不正确,cfc header magic位 校验失败" 366 | }) 367 | } 368 | 369 | let mut crc32_digest = crc32::Digest::new_with_initial(crc32::IEEE, 0u32); 370 | crc32_digest.write(&(read_buffer.as_slice()[..24])); 371 | if cfg_header.cfg_header_crc32 != crc32_digest.sum32() { 372 | turn!(UnpackError { 373 | reason: "文件格式不正确,cfg header crc32 校验失败" 374 | }) 375 | } 376 | } 377 | 378 | let mut decompressor = Decompress::new(true); 379 | 380 | let cfg_header_size = mem::size_of::(); 381 | let data_chunk_header_size = mem::size_of::() as u32; 382 | let mut last_chunk_end_offset = cfg_header_size as u32; 383 | let mut compressed_chunk_overlaying_crc32 = 0u32; 384 | loop { 385 | let data_chunk_header: DataChunkHeader; 386 | { 387 | let mut read_buffer: Vec = vec![0u8; data_chunk_header_size as usize]; 388 | input_stream.read_exact(&mut read_buffer)?; 389 | data_chunk_header = big_endian_config.deserialize(&read_buffer)?; 390 | } 391 | 392 | { 393 | let mut read_buffer: Vec; 394 | if data_chunk_header.chunk_end_offset != 0 { 395 | read_buffer = vec![ 396 | 0u8; 397 | (data_chunk_header.chunk_end_offset 398 | - data_chunk_header_size 399 | - last_chunk_end_offset) as usize 400 | ]; 401 | input_stream.read_exact(&mut read_buffer)?; 402 | last_chunk_end_offset = data_chunk_header.chunk_end_offset; 403 | } else { 404 | read_buffer = Vec::with_capacity(data_chunk_header.after_compressed_size as usize); 405 | input_stream.read_to_end(&mut read_buffer)?; 406 | } 407 | 408 | let mut write_buffer: Vec = 409 | Vec::with_capacity(data_chunk_header.before_compressed_size as usize); 410 | decompressor.decompress_vec( 411 | &read_buffer, 412 | &mut write_buffer, 413 | FlushDecompress::Finish, 414 | )?; 415 | 416 | output_stream.write_all(&write_buffer)?; 417 | decompressor.reset(true); 418 | 419 | let mut crc32_digest = 420 | crc32::Digest::new_with_initial(crc32::IEEE, compressed_chunk_overlaying_crc32); 421 | crc32_digest.write(&read_buffer); 422 | compressed_chunk_overlaying_crc32 = crc32_digest.sum32(); 423 | 424 | if data_chunk_header.chunk_end_offset == 0 { 425 | break; 426 | } 427 | } 428 | } 429 | 430 | if compressed_chunk_overlaying_crc32 != cfg_header.compressed_chunk_overlying_crc32 { 431 | turn!(UnpackError { 432 | reason: "文件数据不正确,压缩数据 CRC32 校验失败" 433 | }) 434 | } 435 | 436 | Ok(()) 437 | } 438 | 439 | fn main() { 440 | use clap::{App, Arg, SubCommand}; 441 | 442 | let matches = App::new("CTCE8 file pack/unpack tool") 443 | .version("0.1.0") 444 | .subcommand( 445 | SubCommand::with_name("pack") 446 | .about("Package the XML file into a CTCE8 CFG file") 447 | .arg( 448 | Arg::with_name("INPUT") 449 | .help("Sets the XML file path") 450 | .required(true) 451 | .index(1), 452 | ) 453 | .arg( 454 | Arg::with_name("OUTPUT") 455 | .help("Sets the CFG file path") 456 | .required(true) 457 | .index(2), 458 | ) 459 | .arg( 460 | Arg::with_name("MODEL") 461 | .help("Sets the device model string") 462 | .required(true) 463 | .index(3), 464 | ), 465 | ) 466 | .subcommand( 467 | SubCommand::with_name("unpack") 468 | .about("Unpack the CTCE8 CFG file into an XML file") 469 | .arg( 470 | Arg::with_name("INPUT") 471 | .help("Sets the CFG file path") 472 | .required(true) 473 | .index(1), 474 | ) 475 | .arg( 476 | Arg::with_name("OUTPUT") 477 | .help("Sets the XML file path") 478 | .required(true) 479 | .index(2), 480 | ), 481 | ) 482 | .get_matches(); 483 | 484 | if let Some(matches) = matches.subcommand_matches("pack") { 485 | if let Err(err) = pack_to_cfg( 486 | matches.value_of("INPUT").unwrap(), 487 | matches.value_of("OUTPUT").unwrap(), 488 | matches.value_of("MODEL").unwrap(), 489 | ) { 490 | println!("{:?}", err); 491 | } 492 | 493 | return; 494 | } 495 | 496 | if let Some(matches) = matches.subcommand_matches("unpack") { 497 | if let Err(err) = unpack_to_xml( 498 | matches.value_of("INPUT").unwrap(), 499 | matches.value_of("OUTPUT").unwrap(), 500 | ) { 501 | println!("{:?}", err); 502 | } 503 | 504 | return; 505 | } 506 | 507 | println!("{}", matches.usage()); 508 | println!(""); 509 | println!( 510 | "Try `{} --help' for more information.", 511 | std::env::current_exe() 512 | .unwrap_or(std::path::Path::new("ctce8_cfg_tool").to_path_buf()) 513 | .file_name() 514 | .unwrap() 515 | .to_str() 516 | .unwrap() 517 | ); 518 | } 519 | --------------------------------------------------------------------------------