├── .envrc ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── assets ├── gm_module_tbl.farc ├── lenitm027.farc ├── mikitm175.farc ├── pv_721_common.farc └── robmot_PV626.farc ├── examples └── farc.rs ├── flake.lock ├── flake.nix └── src ├── archive.rs ├── entry ├── compress.rs ├── encrypt.rs ├── mod.rs └── read.rs ├── lib.rs └── read ├── error.rs ├── mod.rs ├── tests.rs └── utilities.rs /.envrc: -------------------------------------------------------------------------------- 1 | use_flake 2 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .direnv/ 4 | result 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | GH_TOKEN: 7 | DEPLOY_TO_GITHUB: 8 | before_deploy: &1 9 | - git config --local user.name "Waelwindows" 10 | - git config --local user.email "waelwindows@hotmail.com" 11 | - name="farc-$TRAVIS_TAG-$TARGET" 12 | - mkdir $name 13 | - cp target/$TARGET/release/examples/farc $name/ 14 | - cp README.md LICENSE $name/ 15 | - tar czvf $name.tar.gz $name 16 | deploy: &2 17 | provider: releases 18 | api_key: 19 | secure: kmc9Xjy9T0Gvf6k60embsoxUs9ksfMl1UTNp99Ky7hSvoGYM/eFfg2Z1Kq6hBS+uSjAUdEtD0PwUOO1ZyAeMKqOdDnIBKqCvVTjitkRz0T1op0kuQ/s+qQnU+snw5EwKQ5S9UBB+pdGWlh9SBmCjPOAnigohKB8fJsqIgxWt/B6bPCKeahrdA7oMyQ9AAh4NFhnr21QJHRUUQqzpLFl8bKXexZQ45N6DZ7tRTugeMP7OLr8U6H/vXYE6rjM3sW+QkbLPQHh1swIR0naQLxPMyKrH/z1LH5CSSb700wLI6fw4HVTVv6fQgQJFyNypdY/MTNJQL0O4/s+bE+f6KbLjkLVN6AU9MjUyacfn3g9lqufofJJ8QBGm5P3yQhCJlJiZBn8B3bjvBuzxM1CEe3O+sxIktPwD5I9fB0aKEmBspo4L76XrYl8y4ov2YmVrtuaC6dpMJQFNj1n+jgUzOYR3ELrS9Q1fCDvYcMOhVNHiFxsqBt1ik7uWSu7k/AU6yLMdJh0oYOvnFUfRCPfttNjLCD5p9HYTVwZZytszT+jRQ5Bi482RCF9Cnpe3o1SXzgl2ss1RiVYfb/ZF+u34fgHAZUfXaylhUbFij2fdWJrKrZ1yJ7Vr4YALcyfGPwSH58dPe9eFc1PjCbVVLj8WffgEZmtxMFxvNXKckJ8uL9tUQbg= 20 | file: farc-$TRAVIS_TAG-$TARGET.tar.gz 21 | skip_cleanup: true 22 | on: 23 | all_branches: true 24 | tags: true 25 | matrix: 26 | allow_failures: 27 | - rust: nightly 28 | fast_finish: true 29 | cache: cargo 30 | include: 31 | - name: Linux Binary 32 | env: TARGET=x86_64-unknown-linux-musl 33 | rust: nightly 34 | before_script: rustup target add $TARGET 35 | script: cargo build --release --target $TARGET --example=farc 36 | addons: 37 | apt: 38 | packages: 39 | - musl-tools 40 | before_deploy: *1 41 | deploy: *2 42 | - name: macOS Binary 43 | env: MACOSX_DEPLOYMENT_TARGET=10.7 TARGET=x86_64-apple-darwin 44 | os: osx 45 | rust: nightly 46 | script: cargo build --release --target $TARGET --example=farc 47 | install: true 48 | before_deploy: *1 49 | deploy: *2 50 | #deploy: 51 | # provider: releases 52 | # api_key: 53 | # secure: kmc9Xjy9T0Gvf6k60embsoxUs9ksfMl1UTNp99Ky7hSvoGYM/eFfg2Z1Kq6hBS+uSjAUdEtD0PwUOO1ZyAeMKqOdDnIBKqCvVTjitkRz0T1op0kuQ/s+qQnU+snw5EwKQ5S9UBB+pdGWlh9SBmCjPOAnigohKB8fJsqIgxWt/B6bPCKeahrdA7oMyQ9AAh4NFhnr21QJHRUUQqzpLFl8bKXexZQ45N6DZ7tRTugeMP7OLr8U6H/vXYE6rjM3sW+QkbLPQHh1swIR0naQLxPMyKrH/z1LH5CSSb700wLI6fw4HVTVv6fQgQJFyNypdY/MTNJQL0O4/s+bE+f6KbLjkLVN6AU9MjUyacfn3g9lqufofJJ8QBGm5P3yQhCJlJiZBn8B3bjvBuzxM1CEe3O+sxIktPwD5I9fB0aKEmBspo4L76XrYl8y4ov2YmVrtuaC6dpMJQFNj1n+jgUzOYR3ELrS9Q1fCDvYcMOhVNHiFxsqBt1ik7uWSu7k/AU6yLMdJh0oYOvnFUfRCPfttNjLCD5p9HYTVwZZytszT+jRQ5Bi482RCF9Cnpe3o1SXzgl2ss1RiVYfb/ZF+u34fgHAZUfXaylhUbFij2fdWJrKrZ1yJ7Vr4YALcyfGPwSH58dPe9eFc1PjCbVVLj8WffgEZmtxMFxvNXKckJ8uL9tUQbg= 54 | # file: '' 55 | # on: 56 | # repo: Waelwindows/farc 57 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.12.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 16 | dependencies = [ 17 | "winapi", 18 | ] 19 | 20 | [[package]] 21 | name = "arrayvec" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 25 | 26 | [[package]] 27 | name = "atty" 28 | version = "0.2.14" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 31 | dependencies = [ 32 | "hermit-abi", 33 | "libc", 34 | "winapi", 35 | ] 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "cfg-if" 45 | version = "1.0.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 48 | 49 | [[package]] 50 | name = "clap" 51 | version = "2.34.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 54 | dependencies = [ 55 | "ansi_term", 56 | "atty", 57 | "bitflags", 58 | "strsim", 59 | "textwrap", 60 | "unicode-width", 61 | "vec_map", 62 | ] 63 | 64 | [[package]] 65 | name = "crc32fast" 66 | version = "1.3.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 69 | dependencies = [ 70 | "cfg-if", 71 | ] 72 | 73 | [[package]] 74 | name = "enum_dispatch" 75 | version = "0.3.9" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "1693044dcf452888dd3a6a6a0dab67f0652094e3920dfe029a54d2f37d9b7394" 78 | dependencies = [ 79 | "once_cell", 80 | "proc-macro2", 81 | "quote", 82 | "syn", 83 | ] 84 | 85 | [[package]] 86 | name = "farc" 87 | version = "0.1.0" 88 | dependencies = [ 89 | "enum_dispatch", 90 | "flate2", 91 | "nom", 92 | "structopt", 93 | "thiserror", 94 | ] 95 | 96 | [[package]] 97 | name = "flate2" 98 | version = "1.0.25" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" 101 | dependencies = [ 102 | "crc32fast", 103 | "miniz_oxide", 104 | ] 105 | 106 | [[package]] 107 | name = "heck" 108 | version = "0.3.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 111 | dependencies = [ 112 | "unicode-segmentation", 113 | ] 114 | 115 | [[package]] 116 | name = "hermit-abi" 117 | version = "0.1.19" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 120 | dependencies = [ 121 | "libc", 122 | ] 123 | 124 | [[package]] 125 | name = "lazy_static" 126 | version = "1.4.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 129 | 130 | [[package]] 131 | name = "lexical-core" 132 | version = "0.7.6" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 135 | dependencies = [ 136 | "arrayvec", 137 | "bitflags", 138 | "cfg-if", 139 | "ryu", 140 | "static_assertions", 141 | ] 142 | 143 | [[package]] 144 | name = "libc" 145 | version = "0.2.139" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 148 | 149 | [[package]] 150 | name = "memchr" 151 | version = "2.5.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 154 | 155 | [[package]] 156 | name = "miniz_oxide" 157 | version = "0.6.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 160 | dependencies = [ 161 | "adler", 162 | ] 163 | 164 | [[package]] 165 | name = "nom" 166 | version = "5.1.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 169 | dependencies = [ 170 | "lexical-core", 171 | "memchr", 172 | "version_check", 173 | ] 174 | 175 | [[package]] 176 | name = "once_cell" 177 | version = "1.17.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 180 | 181 | [[package]] 182 | name = "proc-macro-error" 183 | version = "1.0.4" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 186 | dependencies = [ 187 | "proc-macro-error-attr", 188 | "proc-macro2", 189 | "quote", 190 | "syn", 191 | "version_check", 192 | ] 193 | 194 | [[package]] 195 | name = "proc-macro-error-attr" 196 | version = "1.0.4" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 199 | dependencies = [ 200 | "proc-macro2", 201 | "quote", 202 | "version_check", 203 | ] 204 | 205 | [[package]] 206 | name = "proc-macro2" 207 | version = "1.0.49" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 210 | dependencies = [ 211 | "unicode-ident", 212 | ] 213 | 214 | [[package]] 215 | name = "quote" 216 | version = "1.0.23" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 219 | dependencies = [ 220 | "proc-macro2", 221 | ] 222 | 223 | [[package]] 224 | name = "ryu" 225 | version = "1.0.12" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 228 | 229 | [[package]] 230 | name = "static_assertions" 231 | version = "1.1.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 234 | 235 | [[package]] 236 | name = "strsim" 237 | version = "0.8.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 240 | 241 | [[package]] 242 | name = "structopt" 243 | version = "0.3.26" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 246 | dependencies = [ 247 | "clap", 248 | "lazy_static", 249 | "structopt-derive", 250 | ] 251 | 252 | [[package]] 253 | name = "structopt-derive" 254 | version = "0.4.18" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 257 | dependencies = [ 258 | "heck", 259 | "proc-macro-error", 260 | "proc-macro2", 261 | "quote", 262 | "syn", 263 | ] 264 | 265 | [[package]] 266 | name = "syn" 267 | version = "1.0.107" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 270 | dependencies = [ 271 | "proc-macro2", 272 | "quote", 273 | "unicode-ident", 274 | ] 275 | 276 | [[package]] 277 | name = "textwrap" 278 | version = "0.11.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 281 | dependencies = [ 282 | "unicode-width", 283 | ] 284 | 285 | [[package]] 286 | name = "thiserror" 287 | version = "1.0.38" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 290 | dependencies = [ 291 | "thiserror-impl", 292 | ] 293 | 294 | [[package]] 295 | name = "thiserror-impl" 296 | version = "1.0.38" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 299 | dependencies = [ 300 | "proc-macro2", 301 | "quote", 302 | "syn", 303 | ] 304 | 305 | [[package]] 306 | name = "unicode-ident" 307 | version = "1.0.6" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 310 | 311 | [[package]] 312 | name = "unicode-segmentation" 313 | version = "1.10.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" 316 | 317 | [[package]] 318 | name = "unicode-width" 319 | version = "0.1.10" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 322 | 323 | [[package]] 324 | name = "vec_map" 325 | version = "0.8.2" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 328 | 329 | [[package]] 330 | name = "version_check" 331 | version = "0.9.4" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 334 | 335 | [[package]] 336 | name = "winapi" 337 | version = "0.3.9" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 340 | dependencies = [ 341 | "winapi-i686-pc-windows-gnu", 342 | "winapi-x86_64-pc-windows-gnu", 343 | ] 344 | 345 | [[package]] 346 | name = "winapi-i686-pc-windows-gnu" 347 | version = "0.4.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 350 | 351 | [[package]] 352 | name = "winapi-x86_64-pc-windows-gnu" 353 | version = "0.4.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 356 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "farc" 3 | version = "0.1.0" 4 | authors = ["Waelwindows "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | nom = "5.0.1" 11 | flate2 = "1.0.12" 12 | enum_dispatch = "0.3.9" 13 | thiserror = "1.0.6" 14 | 15 | [badges] 16 | 17 | # Appveyor: `repository` is required. `branch` is optional; default is `master` 18 | # `service` is optional; valid values are `github` (default), `bitbucket`, and 19 | # `gitlab`; `id` is optional; you can specify the appveyor project id if you 20 | # want to use that instead. `project_name` is optional; use when the repository 21 | # name differs from the appveyor project name. 22 | appveyor = { repository = "Waelwindows/farc", service = "github" } 23 | 24 | # Travis CI: `repository` in format "/" is required. 25 | # `branch` is optional; default is `master` 26 | travis-ci = { repository = "Waelwindows/farc", branch = "master" } 27 | 28 | [dev-dependencies] 29 | structopt = "0.3.3" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Waelwindows 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # farchive [![Build Status](https://travis-ci.com/Waelwindows/farc.svg?branch=master)](https://travis-ci.com/Waelwindows/farc) 2 | A command line tool and library used to create/manipulate SEGA's File Archive format. 3 | 4 | ## Getting Started 5 | 6 | These instructions will get you a copy of the project up and running on your local machine for development and usage. 7 | 8 | ### Prerequisites 9 | 10 | What things you need to install the software 11 | 12 | * git 13 | * [cargo](https://www.rust-lang.org/tools/install) 14 | 15 | ### Installing 16 | 17 | * Clone this repository 18 | ``` 19 | $ git clone https://github.com/Waelwindows/farc.git 20 | ``` 21 | 22 | * Install the project using `cargo install` 23 | 24 | ``` 25 | $ cargo install 26 | ``` 27 | 28 | * (Only for developers) Build/test the project using `cargo` 29 | 30 | ``` 31 | $ cargo build 32 | $ cargo test 33 | ``` 34 | 35 | And then you should be ready to run the program 36 | 37 | ``` 38 | $ farc 39 | farc 0.1.0 40 | Waelwindows 41 | manipulates SEGA File Archive file formats 42 | 43 | USAGE: 44 | farc 45 | 46 | FLAGS: 47 | -h, --help Prints help information 48 | -V, --version Prints version information 49 | 50 | SUBCOMMANDS: 51 | create 52 | extract 53 | help Prints this message or the help of the given subcommand(s) 54 | view 55 | ``` 56 | 57 | ## Versioning 58 | 59 | We use [SemVer](http://semver.org/) for versioning. 60 | 61 | ## License 62 | 63 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 64 | 65 | ## Acknowledgments 66 | 67 | * [DIVA_Tools](https://github.com/Waelwindows/DIVA_Tools) 68 | * The amazing [MikuMikuLibrary](https://github.com/blueskythlikesclouds/MikuMikuLibrary) made by [blueskythlikesclouds](https://github.com/blueskythlikesclouds) 69 | * [s117](https://github.com/s117) for making [DIVAFILE_Tool](https://github.com/s117/DIVAFILE_Tool) 70 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust using rustup for Rust installation 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | ## Build Matrix ## 10 | 11 | # This configuration will setup a build for each channel & target combination (12 windows 12 | # combinations in all). 13 | # 14 | # There are 3 channels: stable, beta, and nightly. 15 | # 16 | # Alternatively, the full version may be specified for the channel to build using that specific 17 | # version (e.g. channel: 1.5.0) 18 | # 19 | # The values for target are the set of windows Rust build targets. Each value is of the form 20 | # 21 | # ARCH-pc-windows-TOOLCHAIN 22 | # 23 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 24 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 25 | # a description of the toolchain differences. 26 | # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of 27 | # toolchains and host triples. 28 | # 29 | # Comment out channel/target combos you do not wish to build in CI. 30 | # 31 | # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands 32 | # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly 33 | # channels to enable unstable features when building for nightly. Or you could add additional 34 | # matrix entries to test different combinations of features. 35 | environment: 36 | matrix: 37 | 38 | #Prefer MSVC toolchain 39 | ### MSVC Toolchains ### 40 | 41 | # # Stable 64-bit MSVC 42 | # - channel: stable 43 | # target: x86_64-pc-windows-msvc 44 | # # Stable 32-bit MSVC 45 | # - channel: stable 46 | # target: i686-pc-windows-msvc 47 | # # Beta 64-bit MSVC 48 | # - channel: beta 49 | # target: x86_64-pc-windows-msvc 50 | # # Beta 32-bit MSVC 51 | # - channel: beta 52 | # target: i686-pc-windows-msvc 53 | # Nightly 64-bit MSVC 54 | - channel: nightly 55 | target: x86_64-pc-windows-msvc 56 | #cargoflags: --features "unstable" 57 | # Nightly 32-bit MSVC 58 | - channel: nightly 59 | target: i686-pc-windows-msvc 60 | #cargoflags: --features "unstable" 61 | 62 | ### Allowed failures ### 63 | 64 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 65 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 66 | # or test failure in the matching channels/targets from failing the entire build. 67 | matrix: 68 | allow_failures: 69 | - channel: nightly 70 | 71 | # If you only care about stable channel build failures, uncomment the following line: 72 | #- channel: beta 73 | 74 | ## Install Script ## 75 | 76 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 77 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 78 | # rustup to install Rust. 79 | # 80 | # For simple configurations, instead of using the build matrix, you can simply set the 81 | # default-toolchain and default-host manually here. 82 | install: 83 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 84 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 85 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 86 | - rustc -vV 87 | - cargo -vV 88 | 89 | ## Build Script ## 90 | 91 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 92 | # the "directory does not contain a project or solution file" error. 93 | build: false 94 | 95 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 96 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 97 | # environment variable. 98 | test_script: 99 | - cargo build --release --verbose %cargoflags% --example=farc 100 | 101 | 102 | # Start builds on tags only (GitHub, BitBucket, GitLab, Gitea) 103 | skip_non_tags: true 104 | 105 | before_deploy: 106 | - ps: | 107 | $NAME = "farc-${env:APPVEYOR_REPO_TAG_NAME}-${env:target}" 108 | New-Item -Path $NAME -ItemType directory 109 | Copy-Item "target/release/examples/farc.exe" "${NAME}/" 110 | Copy-Item "target/release/examples/farc.exe" "farc-${env:APPVEYOR_REPO_TAG_NAME}-${env:target}.exe" 111 | Copy-Item LICENSE "${NAME}/" 112 | Copy-Item README.md "${NAME}/" 113 | 7z a -ttar "${NAME}.tar" "${NAME}" 114 | 7z a "${NAME}.tar.gz" "${NAME}.tar" 115 | Push-AppveyorArtifact "${NAME}.tar.gz" 116 | Push-AppveyorArtifact "farc-${env:APPVEYOR_REPO_TAG_NAME}-${env:target}.exe" 117 | 118 | deploy: 119 | artifact: /.*\.tar.gz/, /.*\.exe/ 120 | description: 'Appveyor Automated Release' 121 | provider: GitHub 122 | draft: false 123 | prerelease: false 124 | auth_token: 125 | secure: nRNMF+FbnpmsD2L5DyshVEJfdJzMrqyET/b0CQ5wSqxR8S/CVCM8Wc/P7uocIy8b 126 | on: 127 | appveyor_repo_tag: true 128 | -------------------------------------------------------------------------------- /assets/gm_module_tbl.farc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva-rust-modding/farc/0e51959a808ea09e60aaacb4c46030ab095d2286/assets/gm_module_tbl.farc -------------------------------------------------------------------------------- /assets/lenitm027.farc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva-rust-modding/farc/0e51959a808ea09e60aaacb4c46030ab095d2286/assets/lenitm027.farc -------------------------------------------------------------------------------- /assets/mikitm175.farc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva-rust-modding/farc/0e51959a808ea09e60aaacb4c46030ab095d2286/assets/mikitm175.farc -------------------------------------------------------------------------------- /assets/pv_721_common.farc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva-rust-modding/farc/0e51959a808ea09e60aaacb4c46030ab095d2286/assets/pv_721_common.farc -------------------------------------------------------------------------------- /assets/robmot_PV626.farc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diva-rust-modding/farc/0e51959a808ea09e60aaacb4c46030ab095d2286/assets/robmot_PV626.farc -------------------------------------------------------------------------------- /examples/farc.rs: -------------------------------------------------------------------------------- 1 | use farc::*; 2 | use std::path::*; 3 | use structopt::*; 4 | 5 | #[derive(StructOpt)] 6 | #[structopt(name = "farc", about = "manipulates SEGA File Archive file formats")] 7 | enum Opt { 8 | #[structopt(name = "create")] 9 | Create { 10 | #[structopt(parse(from_os_str))] 11 | create: PathBuf, 12 | #[structopt(short)] 13 | compress: bool, 14 | #[structopt(short)] 15 | encrypt: bool, 16 | }, 17 | #[structopt(name = "extract")] 18 | Extract { 19 | path: PathBuf, 20 | #[structopt(long, short)] 21 | ///Extract to the root directory of the archive instead of a nested folder 22 | root: bool, 23 | }, 24 | //Only view the archive without extracting 25 | #[structopt(name = "view")] 26 | View { path: PathBuf }, 27 | } 28 | 29 | use std::fs::File; 30 | use std::io::{self, Read}; 31 | 32 | fn main() { 33 | let opts = Opt::from_args(); 34 | 35 | match opts { 36 | Opt::Extract { path, root } => { 37 | let path = Path::new(&path); 38 | let mut file = File::open(&path).expect("Failed to open file"); 39 | let mut input = vec![]; 40 | file.read_to_end(&mut input).unwrap(); 41 | 42 | let (_, farc) = GenericArchive::read(&input).expect("Failed to parse archive"); 43 | let root_dir = path.parent().unwrap(); 44 | let root_dir = if root { 45 | root_dir.to_owned() 46 | } else { 47 | root_dir.join(path.file_stem().unwrap()) 48 | }; 49 | std::fs::create_dir(&root_dir); 50 | match farc { 51 | GenericArchive::Base(a) => extract(&root_dir, &a.entries), 52 | GenericArchive::Compress(a) => extract(&root_dir, &a.entries), 53 | GenericArchive::Extended(a) => { 54 | use ExtendedArchives::*; 55 | match a { 56 | Base(a) => extract(&root_dir, &a.0.entries), 57 | Compress(a) => extract(&root_dir, &a.0.entries), 58 | _ => unimplemented!("Extracting encrypted archives is not yet supported"), 59 | } 60 | } 61 | GenericArchive::Future(a) => { 62 | use FutureArchives::*; 63 | match a { 64 | Base(a) => extract(&root_dir, &a.0.entries), 65 | Compress(a) => extract(&root_dir, &a.0.entries), 66 | _ => unimplemented!("Extracting encrypted archives is not yet supported"), 67 | } 68 | } 69 | }; 70 | } 71 | Opt::View { path } => { 72 | let path = Path::new(&path); 73 | let mut file = File::open(&path).expect("Failed to open file"); 74 | let mut input = vec![]; 75 | file.read_to_end(&mut input).unwrap(); 76 | 77 | let farc = BaseArchive::read(&input) 78 | .expect("Failed to parse archive") 79 | .1; 80 | println!("FArc archive with {} entries", farc.entries.len()); 81 | for (i, entry) in farc.entries.iter().enumerate() { 82 | println!("#{} {}", i + 1, entry.name()); 83 | } 84 | } 85 | _ => (), 86 | }; 87 | } 88 | 89 | fn extract<'a, E: EntryExtract<'a>>( 90 | root_dir: &Path, 91 | entries: &'a [E], 92 | ) -> Result<(), Box> { 93 | for entry in entries { 94 | let mut file = File::create(root_dir.join(&entry.name()))?; 95 | io::copy(&mut entry.extractor(), &mut file)?; 96 | } 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "inputs": { 5 | "flake-compat": "flake-compat", 6 | "flake-utils": "flake-utils", 7 | "nixpkgs": [ 8 | "nixpkgs" 9 | ], 10 | "rust-overlay": "rust-overlay" 11 | }, 12 | "locked": { 13 | "lastModified": 1670546681, 14 | "narHash": "sha256-S33bhME0zPHPEZyZPCsrdQL/4WW/A020PwN+a3z7Q+I=", 15 | "owner": "ipetkov", 16 | "repo": "crane", 17 | "rev": "63f80ee278897e72a1468090278716b5befa5128", 18 | "type": "github" 19 | }, 20 | "original": { 21 | "owner": "ipetkov", 22 | "repo": "crane", 23 | "type": "github" 24 | } 25 | }, 26 | "flake-compat": { 27 | "flake": false, 28 | "locked": { 29 | "lastModified": 1668681692, 30 | "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", 31 | "owner": "edolstra", 32 | "repo": "flake-compat", 33 | "rev": "009399224d5e398d03b22badca40a37ac85412a1", 34 | "type": "github" 35 | }, 36 | "original": { 37 | "owner": "edolstra", 38 | "repo": "flake-compat", 39 | "type": "github" 40 | } 41 | }, 42 | "flake-utils": { 43 | "locked": { 44 | "lastModified": 1667395993, 45 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 46 | "owner": "numtide", 47 | "repo": "flake-utils", 48 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "numtide", 53 | "repo": "flake-utils", 54 | "type": "github" 55 | } 56 | }, 57 | "flake-utils_2": { 58 | "locked": { 59 | "lastModified": 1667395993, 60 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 61 | "owner": "numtide", 62 | "repo": "flake-utils", 63 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "numtide", 68 | "repo": "flake-utils", 69 | "type": "github" 70 | } 71 | }, 72 | "flake-utils_3": { 73 | "locked": { 74 | "lastModified": 1659877975, 75 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 76 | "owner": "numtide", 77 | "repo": "flake-utils", 78 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 79 | "type": "github" 80 | }, 81 | "original": { 82 | "owner": "numtide", 83 | "repo": "flake-utils", 84 | "type": "github" 85 | } 86 | }, 87 | "nixpkgs": { 88 | "locked": { 89 | "lastModified": 1670616688, 90 | "narHash": "sha256-QUl+vVoWWuUxOxa+NhyUWbXfx9gph4o1Q1u7n9c5vqo=", 91 | "owner": "NixOS", 92 | "repo": "nixpkgs", 93 | "rev": "d4288c8e3ff5ada52390ae0bdd3de89a6054979c", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "NixOS", 98 | "repo": "nixpkgs", 99 | "type": "github" 100 | } 101 | }, 102 | "nixpkgs_2": { 103 | "locked": { 104 | "lastModified": 1665296151, 105 | "narHash": "sha256-uOB0oxqxN9K7XGF1hcnY+PQnlQJ+3bP2vCn/+Ru/bbc=", 106 | "owner": "NixOS", 107 | "repo": "nixpkgs", 108 | "rev": "14ccaaedd95a488dd7ae142757884d8e125b3363", 109 | "type": "github" 110 | }, 111 | "original": { 112 | "owner": "NixOS", 113 | "ref": "nixpkgs-unstable", 114 | "repo": "nixpkgs", 115 | "type": "github" 116 | } 117 | }, 118 | "root": { 119 | "inputs": { 120 | "crane": "crane", 121 | "flake-utils": "flake-utils_2", 122 | "nixpkgs": "nixpkgs", 123 | "rust-overlay": "rust-overlay_2" 124 | } 125 | }, 126 | "rust-overlay": { 127 | "inputs": { 128 | "flake-utils": [ 129 | "crane", 130 | "flake-utils" 131 | ], 132 | "nixpkgs": [ 133 | "crane", 134 | "nixpkgs" 135 | ] 136 | }, 137 | "locked": { 138 | "lastModified": 1670034122, 139 | "narHash": "sha256-EqmuOKucPWtMvCZtHraHr3Q3bgVszq1x2PoZtQkUuEk=", 140 | "owner": "oxalica", 141 | "repo": "rust-overlay", 142 | "rev": "a0d5773275ecd4f141d792d3a0376277c0fc0b65", 143 | "type": "github" 144 | }, 145 | "original": { 146 | "owner": "oxalica", 147 | "repo": "rust-overlay", 148 | "type": "github" 149 | } 150 | }, 151 | "rust-overlay_2": { 152 | "inputs": { 153 | "flake-utils": "flake-utils_3", 154 | "nixpkgs": "nixpkgs_2" 155 | }, 156 | "locked": { 157 | "lastModified": 1670552927, 158 | "narHash": "sha256-lCE51eAGrAFS4k9W5aDGFpVtOAwQQ/rFMN80PCDh0vo=", 159 | "owner": "oxalica", 160 | "repo": "rust-overlay", 161 | "rev": "a0fdafd18c9cf599fde17fbaf07dbb20fa57eecb", 162 | "type": "github" 163 | }, 164 | "original": { 165 | "owner": "oxalica", 166 | "repo": "rust-overlay", 167 | "type": "github" 168 | } 169 | } 170 | }, 171 | "root": "root", 172 | "version": 7 173 | } 174 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "farc flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | rust-overlay.url = "github:oxalica/rust-overlay"; 8 | crane = { 9 | url = "github:ipetkov/crane"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | }; 13 | 14 | outputs = { 15 | self, 16 | nixpkgs, 17 | flake-utils, 18 | rust-overlay, 19 | crane, 20 | }: let 21 | in 22 | flake-utils.lib.eachDefaultSystem 23 | (system: let 24 | pkgs = import nixpkgs { 25 | inherit system; 26 | overlays = [ (import rust-overlay) ]; 27 | }; 28 | craneLib = crane.lib.${system}; 29 | src = craneLib.cleanCargoSource ./.; 30 | cargoArtifacts = craneLib.buildDepsOnly { 31 | inherit src; 32 | }; 33 | in rec { 34 | packages = rec { 35 | farc = craneLib.buildPackage { 36 | inherit src cargoArtifacts; 37 | 38 | # Add extra inputs here or any other derivation settings 39 | doCheck = false; 40 | # buildInputs = []; 41 | # nativeBuildInputs = []; 42 | }; 43 | farc-app = craneLib.buildPackage { 44 | inherit src cargoArtifacts; 45 | 46 | cargoExtraArgs = "--example farc"; 47 | 48 | # Add extra inputs here or any other derivation settings 49 | doCheck = false; 50 | # buildInputs = []; 51 | # nativeBuildInputs = []; 52 | }; 53 | default = farc-app; 54 | }; 55 | apps = rec { 56 | farc = { 57 | type = "app"; 58 | program = "${packages.farc-app}/bin/farc"; 59 | }; 60 | default = farc; 61 | }; 62 | devShells.default = with pkgs; pkgs.mkShell rec { 63 | nativeBuildInputs = [ 64 | pkg-config 65 | ]; 66 | buildInputs = [ 67 | (rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { 68 | extensions = [ "rust-src" "rust-analyzer" ]; 69 | targets = []; 70 | })) 71 | ]; 72 | }; 73 | } 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/archive.rs: -------------------------------------------------------------------------------- 1 | use crate::entry::compress::*; 2 | use crate::entry::*; 3 | 4 | pub enum GenericArchive<'a> { 5 | Base(BaseArchive<'a>), 6 | Compress(CompressArchive<'a>), 7 | Extended(ExtendedArchives<'a>), 8 | Future(FutureArchives<'a>), 9 | } 10 | 11 | pub type BaseArchive<'a> = BasicArchive>; 12 | pub type CompressArchive<'a> = BasicArchive>; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub struct BasicArchive { 16 | pub align: u32, 17 | pub entries: Vec, 18 | } 19 | 20 | #[derive(Debug, PartialEq)] 21 | pub struct ExtendArchive(pub BasicArchive); 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub enum ExtendedArchives<'a> { 25 | Base(ExtendArchive>), 26 | Compress(ExtendArchive>), 27 | Encrypt(ExtendArchive>>), 28 | CompressEncrypt(ExtendArchive>>), 29 | } 30 | 31 | #[derive(Debug, PartialEq)] 32 | pub struct FutureArchive(pub BasicArchive); 33 | 34 | #[derive(Debug, PartialEq)] 35 | pub enum FutureArchives<'a> { 36 | Base(FutureArchive>), 37 | Compress(FutureArchive>), 38 | Encrypt(FutureArchive>>), 39 | CompressEncrypt(FutureArchive>>), 40 | } 41 | -------------------------------------------------------------------------------- /src/entry/compress.rs: -------------------------------------------------------------------------------- 1 | use flate2::read::GzDecoder; 2 | 3 | use enum_dispatch::*; 4 | 5 | use super::*; 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub struct CompressedEntry<'a> { 9 | pub(crate) entry: MemoryEntry<'a>, 10 | pub(crate) original_len: u32, 11 | } 12 | 13 | #[enum_dispatch(Entry)] 14 | #[derive(Debug, PartialEq)] 15 | pub enum Compressor<'a> { 16 | Compress(BaseEntry<'a>), 17 | Compressed(CompressedEntry<'a>), 18 | } 19 | 20 | use std::io::Read; 21 | #[enum_dispatch(Read)] 22 | pub enum Decompressor<'a> { 23 | Decompress(GzDecoder<&'a [u8]>), 24 | Uncompressed(&'a [u8]), 25 | } 26 | 27 | impl<'a> Read for Decompressor<'a> { 28 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 29 | match self { 30 | Decompressor::Decompress(gz) => gz.read(buf), 31 | Decompressor::Uncompressed(b) => b.read(buf), 32 | } 33 | } 34 | } 35 | 36 | impl<'a> EntryExtract<'a> for CompressedEntry<'a> { 37 | type Extractor = GzDecoder<&'a [u8]>; 38 | 39 | fn extractor(&'a self) -> Self::Extractor { 40 | GzDecoder::new(&self.entry.data) 41 | } 42 | } 43 | 44 | impl<'a> EntryExtract<'a> for Compressor<'a> { 45 | type Extractor = Decompressor<'a>; 46 | 47 | fn extractor(&'a self) -> Self::Extractor { 48 | match self { 49 | Compressor::Compress(e) => Decompressor::Uncompressed(e.extractor()), 50 | Compressor::Compressed(e) => Decompressor::Decompress(e.extractor()), 51 | } 52 | } 53 | } 54 | 55 | impl<'a> Entry for CompressedEntry<'a> { 56 | fn name(&self) -> &str { 57 | &self.entry.name 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/entry/encrypt.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Encryptor { 5 | Encrypt(E), 6 | Encrypted(E::Decrypt), 7 | } 8 | 9 | pub trait Encrypt: Entry { 10 | type Decrypt: Entry; 11 | 12 | fn encrypt(self) -> Encryptor 13 | where 14 | Self: Sized, 15 | { 16 | Encryptor::Encrypt(self) 17 | } 18 | } 19 | 20 | impl Entry for Encryptor { 21 | fn name(&self) -> &str { 22 | match self { 23 | Encryptor::Encrypt(e) => e.name(), 24 | Encryptor::Encrypted(e) => e.name(), 25 | } 26 | } 27 | } 28 | 29 | impl<'a> Encrypt for MemoryEntry<'a> { 30 | type Decrypt = Self; 31 | } 32 | impl<'a> Encrypt for BaseEntry<'a> { 33 | type Decrypt = MemoryEntry<'a>; 34 | } 35 | impl<'a> Encrypt for Compressor<'a> { 36 | type Decrypt = CompressedEntry<'a>; 37 | } 38 | -------------------------------------------------------------------------------- /src/entry/mod.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::*; 2 | 3 | use std::borrow::Cow; 4 | use std::io; 5 | use std::path::PathBuf; 6 | 7 | pub mod compress; 8 | pub mod encrypt; 9 | pub(crate) mod read; 10 | 11 | pub use self::compress::*; 12 | pub use self::encrypt::*; 13 | 14 | #[derive(Debug, PartialEq)] 15 | ///In-memory data stream 16 | /// 17 | ///Represents an in-memory data stream 18 | ///It's the most common entry type 19 | pub struct MemoryEntry<'a> { 20 | pub name: Cow<'a, str>, 21 | pub data: Cow<'a, [u8]>, 22 | } 23 | 24 | #[derive(Debug, PartialEq)] 25 | ///A file to be encoded as an entry 26 | pub struct FileEntry { 27 | path: PathBuf, 28 | } 29 | 30 | #[enum_dispatch(Entry)] 31 | #[derive(Debug, PartialEq)] 32 | pub enum BaseEntry<'a> { 33 | Memory(MemoryEntry<'a>), 34 | File(FileEntry), 35 | } 36 | 37 | #[enum_dispatch] 38 | pub trait Entry { 39 | fn name(&self) -> &str; 40 | } 41 | 42 | pub trait EntryExtract<'a>: Entry { 43 | type Extractor: io::Read; 44 | 45 | fn extractor(&'a self) -> Self::Extractor; 46 | } 47 | 48 | impl<'a> Entry for MemoryEntry<'a> { 49 | fn name(&self) -> &str { 50 | &self.name 51 | } 52 | } 53 | 54 | impl Entry for FileEntry { 55 | fn name(&self) -> &str { 56 | self.path.file_name().unwrap().to_str().unwrap() 57 | } 58 | } 59 | 60 | impl<'a> EntryExtract<'a> for MemoryEntry<'a> { 61 | type Extractor = &'a [u8]; 62 | 63 | fn extractor(&self) -> &[u8] { 64 | &self.data 65 | } 66 | } 67 | 68 | impl<'a> EntryExtract<'a> for BaseEntry<'a> { 69 | type Extractor = &'a [u8]; 70 | 71 | fn extractor(&self) -> &[u8] { 72 | match self { 73 | BaseEntry::Memory(e) => e.extractor(), 74 | //Other entries shouldn't be extracted 75 | _ => &[], 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/entry/read.rs: -------------------------------------------------------------------------------- 1 | use nom::number::complete::*; 2 | 3 | use super::*; 4 | use crate::read::utilities::*; 5 | 6 | #[enum_dispatch] 7 | pub trait ReadEntry<'a>: Entry + Sized { 8 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self>; 9 | } 10 | 11 | pub trait BasicEntry<'a>: ReadEntry<'a> {} 12 | pub trait ExtendEntry<'a>: ReadEntry<'a> { 13 | const MODE: u32; 14 | } 15 | 16 | impl<'a> ReadEntry<'a> for MemoryEntry<'a> { 17 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self> { 18 | let (i, name) = string(i)?; 19 | let (i, pos) = be_usize(i)?; 20 | let (i, len) = be_usize(i)?; 21 | let data = slice_input(i0, pos..pos + len)?.into(); 22 | Ok((i, MemoryEntry { name, data })) 23 | } 24 | } 25 | 26 | //Should be obselete once `enum_dispatch` supports multiple traits 27 | //See: https://gitlab.com/antonok/enum_dispatch/issues/3 28 | impl<'a> ReadEntry<'a> for BaseEntry<'a> { 29 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self> { 30 | let (i, entry) = MemoryEntry::read(i0, &i)?; 31 | Ok((i, entry.into())) 32 | } 33 | } 34 | 35 | impl<'a> ReadEntry<'a> for CompressedEntry<'a> { 36 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self> { 37 | let (i, entry) = MemoryEntry::read(i0, &i)?; 38 | let (i, original_len) = be_u32(i)?; 39 | Ok(( 40 | i, 41 | CompressedEntry { 42 | entry, 43 | original_len, 44 | }, 45 | )) 46 | } 47 | } 48 | 49 | //Should be obselete once `enum_dispatch` supports multiple traits 50 | //See: https://gitlab.com/antonok/enum_dispatch/issues/3 51 | impl<'a> ReadEntry<'a> for Compressor<'a> { 52 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self> { 53 | let (i, entry) = CompressedEntry::read(i0, &i)?; 54 | Ok((i, entry.into())) 55 | } 56 | } 57 | 58 | impl<'a, E: ReadEntry<'a> + Encrypt> ReadEntry<'a> for Encryptor { 59 | fn read(i0: &'a [u8], i: &'a [u8]) -> Result<'a, Self> { 60 | let (i, entry) = E::read(i0, &i)?; 61 | Ok((i, entry.encrypt())) 62 | } 63 | } 64 | 65 | impl<'a> BasicEntry<'a> for MemoryEntry<'a> {} 66 | impl<'a> BasicEntry<'a> for BaseEntry<'a> {} 67 | impl<'a> BasicEntry<'a> for CompressedEntry<'a> {} 68 | impl<'a> BasicEntry<'a> for Compressor<'a> {} 69 | 70 | impl<'a> ExtendEntry<'a> for MemoryEntry<'a> { 71 | const MODE: u32 = 0; 72 | } 73 | impl<'a> ExtendEntry<'a> for BaseEntry<'a> { 74 | const MODE: u32 = 0; 75 | } 76 | impl<'a> ExtendEntry<'a> for CompressedEntry<'a> { 77 | const MODE: u32 = 2; 78 | } 79 | impl<'a> ExtendEntry<'a> for Compressor<'a> { 80 | const MODE: u32 = 2; 81 | } 82 | impl<'a, E: ExtendEntry<'a> + Encrypt> ExtendEntry<'a> for Encryptor { 83 | const MODE: u32 = E::MODE | 4; 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | 90 | const INPUT: &[u8] = include_bytes!("../../assets/robmot_PV626.farc"); 91 | const COMP: &[u8] = include_bytes!("../../assets/gm_module_tbl.farc"); 92 | 93 | #[test] 94 | fn read_memory() { 95 | let (_, entry) = MemoryEntry::read(INPUT, &INPUT[0xC..]).unwrap(); 96 | assert_eq!(entry.name, "mot_PV626.bin"); 97 | assert_eq!(&entry.data[..4], &[0x20, 0, 0, 0]); 98 | } 99 | 100 | #[test] 101 | fn read_compressed() { 102 | let (_, comp) = CompressedEntry::read(COMP, &COMP[0xC..]).unwrap(); 103 | assert_eq!(&comp.entry.name, "gm_module_id.bin"); 104 | assert_eq!(comp.original_len, 21050); 105 | assert_eq!(&comp.entry.data[..4], &[0x1F, 0x8B, 8, 8]); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod archive; 2 | pub mod entry; 3 | pub mod read; 4 | 5 | pub use crate::archive::*; 6 | pub use crate::entry::*; 7 | -------------------------------------------------------------------------------- /src/read/error.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | use thiserror::*; 3 | 4 | #[derive(Error, Debug)] 5 | #[error("A parser error has occured: {1}")] 6 | pub struct ParserError<'a>(pub &'a [u8], pub ParserErrorKind<'a>); 7 | 8 | #[derive(Error, Debug)] 9 | pub enum ParserErrorKind<'a> { 10 | #[error("Invalid slice offset, most likely corrupt archive")] 11 | InvalidOffset, 12 | #[error("Invalid magic expected farc, found {0:?}")] 13 | InvalidMagic(&'a str), 14 | #[error("Invalid mode, expected {expected} found {found}.\nMost likely corrupted archive or encrypted future tone archive")] 15 | InvalidMode { expected: u32, found: u32 }, 16 | #[error("Invalid version detected, expected {expected} found {found}.\nMost likely found future tone archive while expecting extended and vice versa")] 17 | InvalidVersion { expected: u32, found: u32 }, 18 | #[error("String overflew, couldn't find null byte")] 19 | StringOverflow, 20 | #[error("{0:?}")] 21 | Other(ErrorKind), 22 | } 23 | 24 | impl<'a> ParseError<&'a [u8]> for ParserError<'a> { 25 | fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self { 26 | Self(input, ParserErrorKind::Other(kind)) 27 | } 28 | 29 | fn append(_: &[u8], _: ErrorKind, other: Self) -> Self { 30 | other 31 | } 32 | } 33 | 34 | impl<'a> From<(&'a [u8], ErrorKind)> for ParserError<'a> { 35 | fn from((i, err): (&'a [u8], ErrorKind)) -> Self { 36 | Self::from_error_kind(i, err) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | #[test] 44 | fn convert() { 45 | let i: &[u8] = &[]; 46 | let err = (i, ErrorKind::IsNot); 47 | let _err2: ParserError = err.into(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/read/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, bytes::complete::*, combinator::*, error::context, multi::many1, 3 | number::complete::*, sequence::pair, *, 4 | }; 5 | 6 | use self::error::*; 7 | use super::*; 8 | use crate::entry::read::*; 9 | 10 | use std::borrow::Cow; 11 | 12 | pub mod error; 13 | #[cfg(test)] 14 | mod tests; 15 | pub(crate) mod utilities; 16 | 17 | use self::utilities::*; 18 | 19 | impl<'a> GenericArchive<'a> { 20 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 21 | alt(( 22 | map(BaseArchive::read, GenericArchive::Base), 23 | map(CompressArchive::read, GenericArchive::Compress), 24 | map(ExtendedArchives::read, GenericArchive::Extended), 25 | map(FutureArchives::read, GenericArchive::Future), 26 | ))(i0) 27 | } 28 | } 29 | 30 | impl<'a, E: BasicEntry<'a>> BasicArchive { 31 | fn read_magic(i0: &'a [u8], magic: &'static str) -> Result<'a, Self> { 32 | let (i, _) = tag(magic)(i0)?; 33 | let (i, bs) = be_usize(i)?; 34 | let (i, align) = be_u32(i)?; 35 | //panic!("{} {} {}", bs, align, bs-0xC); 36 | let entry_read = |i: &'a [u8]| E::read(i0, i); 37 | let (_, entries) = slice(many1(entry_read), 0xC..0xC + bs - 4)(i0)?; 38 | Ok((i, BasicArchive { align, entries })) 39 | } 40 | } 41 | impl<'a> BaseArchive<'a> { 42 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 43 | Self::read_magic(i0, "FArc") 44 | } 45 | } 46 | impl<'a> CompressArchive<'a> { 47 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 48 | Self::read_magic(i0, "FArC") 49 | } 50 | } 51 | impl<'a, E: ExtendEntry<'a>> ExtendArchive { 52 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 53 | let (i, _) = tag("FARC")(i0)?; 54 | let (i, bs) = be_usize(i)?; 55 | let (i, mode) = be_u32(i)?; 56 | if mode != E::MODE { 57 | return Err(Err::Error(ParserError( 58 | i, 59 | ParserErrorKind::InvalidMode { 60 | expected: E::MODE, 61 | found: mode, 62 | }, 63 | ))); 64 | } 65 | //skip 4 bytes 66 | let i = &i[4..]; 67 | let (i, align) = be_u32(i)?; 68 | let (i, _) = context( 69 | "Future FARC detected", 70 | map_opt(be_u32, |m| if 0 == m { Some(true) } else { None }), 71 | )(i)?; 72 | //panic!("{} {} {}", bs, align, bs-0xC); 73 | let entry_read = |i: &'a [u8]| E::read(i0, i); 74 | let (_, entries) = slice(many1(entry_read), 0x1C..0x1C + bs - 20)(i0)?; 75 | Ok((i, ExtendArchive(BasicArchive { align, entries }))) 76 | } 77 | } 78 | impl<'a> ExtendedArchives<'a> { 79 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 80 | alt(( 81 | map(ExtendArchive::read, ExtendedArchives::Base), 82 | map(ExtendArchive::read, ExtendedArchives::Compress), 83 | map(ExtendArchive::read, ExtendedArchives::Encrypt), 84 | map(ExtendArchive::read, ExtendedArchives::CompressEncrypt), 85 | ))(i0) 86 | } 87 | } 88 | impl<'a, E: ExtendEntry<'a>> FutureArchive { 89 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 90 | let (i, _) = tag("FARC")(i0)?; 91 | let (i, bs) = be_usize(i)?; 92 | let (i, mode) = be_u32(i)?; 93 | if mode != E::MODE { 94 | return Err(Err::Error(ParserError( 95 | i, 96 | ParserErrorKind::InvalidMode { 97 | expected: E::MODE, 98 | found: mode, 99 | }, 100 | ))); 101 | } 102 | //skip 4 bytes 103 | let i = &i[4..]; 104 | let (i, align) = be_u32(i)?; 105 | let (i, _) = context( 106 | "Normal FARC detected", 107 | map_opt(be_u32, |m| if 1 == m { Some(true) } else { None }), 108 | )(i)?; 109 | //skip 8 bytes 110 | let i = &i[8..]; 111 | let entry_read = |i: &'a [u8]| E::read(i0, i); 112 | let (_, entries) = slice( 113 | many1(map(pair(entry_read, be_u32), |(e, _)| e)), 114 | 0x20..0x20 + bs - 24, 115 | )(i0)?; 116 | Ok((i, FutureArchive(BasicArchive { align, entries }))) 117 | } 118 | } 119 | 120 | impl<'a> FutureArchives<'a> { 121 | pub fn read(i0: &'a [u8]) -> Result<'a, Self> { 122 | alt(( 123 | map(FutureArchive::read, FutureArchives::Base), 124 | map(FutureArchive::read, FutureArchives::Compress), 125 | map(FutureArchive::read, FutureArchives::Encrypt), 126 | map(FutureArchive::read, FutureArchives::CompressEncrypt), 127 | ))(i0) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/read/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use nom::dbg_dmp; 3 | 4 | const INPUT: &[u8] = include_bytes!("../../assets/robmot_PV626.farc"); 5 | const COMP: &[u8] = include_bytes!("../../assets/gm_module_tbl.farc"); 6 | const FARC: &[u8] = include_bytes!("../../assets/pv_721_common.farc"); 7 | const FUTURE: &[u8] = include_bytes!("../../assets/lenitm027.farc"); 8 | 9 | #[test] 10 | fn read_base() { 11 | let (_, farc) = BaseArchive::read(INPUT).unwrap(); 12 | let entry = BaseEntry::Memory(MemoryEntry { 13 | name: "mot_PV626.bin".into(), 14 | data: INPUT[0x22..][..15305208].into(), 15 | }); 16 | assert_eq!(entry, farc.entries[0]); 17 | } 18 | #[test] 19 | fn read_compressed() { 20 | let (_, farc) = CompressArchive::read(COMP).unwrap(); 21 | let entry: Compressor = CompressedEntry { 22 | entry: MemoryEntry { 23 | name: "gm_module_id.bin".into(), 24 | data: COMP[41..][..3827].into(), 25 | }, 26 | original_len: 21050, 27 | } 28 | .into(); 29 | assert_eq!(entry, farc.entries[0]); 30 | } 31 | #[test] 32 | fn read_extended_encrypt_compres() { 33 | let (_, farc) = ExtendArchive::>>::read(FARC).unwrap(); 34 | for entry in &farc.0.entries { 35 | println!("{}", &entry.name()); 36 | } 37 | //pv_721_mouth.dsc 38 | //pv_721_scene.dsc 39 | //pv_721_success_mouth.dsc 40 | //pv_721_success_scene.dsc 41 | //pv_721_system.dsc 42 | assert_eq!(farc.0.entries[0].name(), "pv_721_mouth.dsc"); 43 | assert_eq!(farc.0.entries[1].name(), "pv_721_scene.dsc"); 44 | assert_eq!(farc.0.entries[2].name(), "pv_721_success_mouth.dsc"); 45 | assert_eq!(farc.0.entries[3].name(), "pv_721_success_scene.dsc"); 46 | assert_eq!(farc.0.entries[4].name(), "pv_721_system.dsc"); 47 | } 48 | #[test] 49 | fn read_future_compressed() { 50 | let (_, farc) = dbg_dmp(FutureArchive::>::read, "future")(FUTURE).unwrap(); 51 | for entry in &farc.0.entries { 52 | println!("{} {:#X}", entry.name(), entry.original_len); 53 | } 54 | assert_eq!(farc.0.entries[0].name(), "lenitm027_obj.bin"); 55 | assert_eq!(farc.0.entries[1].name(), "lenitm027_tex.bin"); 56 | } 57 | -------------------------------------------------------------------------------- /src/read/utilities.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn string(i: &[u8]) -> Result> { 4 | is_not::<_, _, ParserError<'_>>("\x00")(i) 5 | .map(|(i2, s)| (&i2[1..], String::from_utf8_lossy(s))) 6 | .map_err(|_| Err::Error(ParserError(i, ParserErrorKind::StringOverflow))) 7 | } 8 | use nom::error::ParseError; 9 | pub(crate) fn be_usize<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], usize, E> { 10 | map(be_u32, |x| x as usize)(i) 11 | } 12 | use std::slice::SliceIndex; 13 | pub(crate) fn slice_input(i: &[u8], s: S) -> core::result::Result<&[u8], Err>> 14 | where 15 | S: SliceIndex<[u8], Output = [u8]>, 16 | { 17 | i.get(s) 18 | .ok_or(Err::Failure(ParserError(i, ParserErrorKind::InvalidOffset))) 19 | } 20 | pub(crate) fn slice<'a, S, F, O, E>(f: F, s: S) -> impl FnOnce(&'a [u8]) -> Result 21 | where 22 | S: SliceIndex<[u8], Output = [u8]>, 23 | F: Fn(&'a [u8]) -> IResult<&'a [u8], O, E>, 24 | ParserError<'a>: From, 25 | { 26 | move |i: &'a [u8]| f(slice_input(i, s)?).map_err(Err::convert) 27 | } 28 | 29 | pub(crate) type Result<'a, O> = IResult<&'a [u8], O, ParserError<'a>>; 30 | --------------------------------------------------------------------------------