mirror of
https://github.com/redstrate/Physis.git
synced 2025-04-19 17:36:50 +00:00
Add initial files
This commit is contained in:
commit
389c3fe00b
33 changed files with 3754 additions and 0 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
.idea/
|
||||
.DS_Store
|
||||
.directory
|
733
Cargo.lock
generated
Executable file
733
Cargo.lock
generated
Executable file
|
@ -0,0 +1,733 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "array-init"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "binrw"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4abb4fd60add897b9e8827e0d5fa6c2ca129ece2432d9aa13454b21ac2ecc18f"
|
||||
dependencies = [
|
||||
"array-init",
|
||||
"binrw_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binrw_derive"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a36ff195a3a1a82d5eeb98e0069bdf3ea076042d28591396d9020fac763bf66f"
|
||||
dependencies = [
|
||||
"owo-colors",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitfield-struct"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0baacb7d5b6e94369201d5807e086d3f36e2bd35057ca2c5a680af3737e35fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac2bd7a1eb07da9ac757c923f69373deb7bc2ba5efc951b873bcb5e693992dca"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.5"
|
||||
source = "git+https://github.com/bheisler/criterion.rs?branch=version-0.4#2934163518a75bf037c7467c0a1d28443203eabf"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tabwriter",
|
||||
"termcolor",
|
||||
"tinytemplate",
|
||||
"unicode-width",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.4"
|
||||
source = "git+https://github.com/bheisler/criterion.rs?branch=version-0.4#2934163518a75bf037c7467c0a1d28443203eabf"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
|
||||
|
||||
[[package]]
|
||||
name = "physis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitfield-struct",
|
||||
"crc",
|
||||
"criterion",
|
||||
"libz-sys",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabwriter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
26
Cargo.toml
Executable file
26
Cargo.toml
Executable file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "physis"
|
||||
version = "0.1.0"
|
||||
authors = ["Joshua Goins <josh@redstrate.com>"]
|
||||
edition = "2021"
|
||||
description = "Interact with XIV game data."
|
||||
license = "GPL-3.0"
|
||||
homepage = "https://xiv.zone/physis"
|
||||
repository = "https://git.sr.ht/~redstrate/physis"
|
||||
keywords = ["ffxiv", "modding"]
|
||||
categories = ["parsing"]
|
||||
|
||||
[[bench]]
|
||||
name = "physis_benchmark"
|
||||
harness = false
|
||||
|
||||
[dev-dependencies]
|
||||
# criterion is not updated to 0.4 yet, which removes an unmaintained package
|
||||
criterion = { git = "https://github.com/bheisler/criterion.rs", branch="version-0.4", version="0.3.5" }
|
||||
|
||||
[dependencies]
|
||||
crc = "3.0.0"
|
||||
binrw = "0.9.2"
|
||||
libz-sys = { version = "1.1.8", default-features = false }
|
||||
bitfield-struct = "0.1.7"
|
||||
paste = "1.0.7"
|
674
LICENSE
Normal file
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
31
README.md
Executable file
31
README.md
Executable file
|
@ -0,0 +1,31 @@
|
|||
# Physis
|
||||
|
||||
Framework for interacting with FFXIV data, and successor to [libxiv](https://git.sr.ht/~redstrate/libxiv). This intended for
|
||||
developers writing modding tools, launchers and other programs.
|
||||
|
||||
**Note:** This library is still experimental, and no releases are planned. I'm currently busy with bringing all of libxiv's
|
||||
features over.
|
||||
|
||||
## Goals
|
||||
* Make it extremely easy for people to tinker around with game data.
|
||||
* Parsing data should be safe, and unit tested vigorously.
|
||||
* Minimal dependencies ;-) All dependencies are also checked by `cargo deny`.
|
||||
|
||||
## Features
|
||||
|
||||
* Apply game patches, enabling custom launchers to patch the game.
|
||||
* Blockfish ciphers for encrypting and decrypting SqexArg.
|
||||
* Parse various game formats:
|
||||
* INDEX
|
||||
* DAT
|
||||
* ZiPatch
|
||||
|
||||
## Development
|
||||
|
||||
If you're interested to see how these formats work in more detail, see [xiv.dev](https://xiv.dev/) and [docs.xiv.zone](https://docs.xiv.zone)!
|
||||
|
||||
Some tests and benchmarks require the environment variable `FFXIV_GAME_DIR` to be set.
|
||||
|
||||
## Contributing
|
||||
|
||||
The best way you can help is by [monetarily supporting me](https://redstrate.com/about/) or by submitting patches to help fix bugs or add functionality!
|
32
benches/physis_benchmark.rs
Executable file
32
benches/physis_benchmark.rs
Executable file
|
@ -0,0 +1,32 @@
|
|||
use std::env;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use physis::sqpack::calculate_hash;
|
||||
|
||||
fn reload_repos() {
|
||||
let game_dir = env::var("FFXIV_GAME_DIR").unwrap();
|
||||
let mut gamedata = physis::gamedata::GameData::from_existing(format!("{}/game", game_dir).as_str()).unwrap();
|
||||
|
||||
gamedata.reload_repositories();
|
||||
}
|
||||
|
||||
fn bench_calculate_hash() {
|
||||
calculate_hash("exd/root.exl");
|
||||
}
|
||||
|
||||
fn fetch_data() {
|
||||
let game_dir = env::var("FFXIV_GAME_DIR").unwrap();
|
||||
let mut gamedata = physis::gamedata::GameData::from_existing(format!("{}/game", game_dir).as_str()).unwrap();
|
||||
|
||||
gamedata.reload_repositories();
|
||||
|
||||
gamedata.extract("exd/root.exl");
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("hash calc", |b| b.iter(|| bench_calculate_hash()));
|
||||
c.bench_function("gamedata reloading repositories", |b| b.iter(|| reload_repos()));
|
||||
c.bench_function("gamedata extract", |b| b.iter(|| fetch_data()));
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
28
deny.toml
Executable file
28
deny.toml
Executable file
|
@ -0,0 +1,28 @@
|
|||
[advisories]
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "deny"
|
||||
yanked = "deny"
|
||||
notice = "warn"
|
||||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
allow = [
|
||||
"Unicode-DFS-2016" # used for unicode-ident
|
||||
]
|
||||
copyleft = "allow"
|
||||
allow-osi-fsf-free = "both"
|
||||
default = "deny"
|
||||
confidence-threshold = 0.8
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "warn"
|
||||
highlight = "all"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = ["https://github.com/bheisler/criterion.rs"]
|
0
resources/tests/invalid_sqpack/game/sqpack/invalid.txt
Executable file
0
resources/tests/invalid_sqpack/game/sqpack/invalid.txt
Executable file
3
resources/tests/test.exl
Executable file
3
resources/tests/test.exl
Executable file
|
@ -0,0 +1,3 @@
|
|||
EXLT,2
|
||||
Foo,4
|
||||
Bar,-1
|
1
resources/tests/valid_sqpack/game/ffxivgame.ver
Executable file
1
resources/tests/valid_sqpack/game/ffxivgame.ver
Executable file
|
@ -0,0 +1 @@
|
|||
1.0.1
|
1
resources/tests/valid_sqpack/game/sqpack/ex1/ex1.ver
Executable file
1
resources/tests/valid_sqpack/game/sqpack/ex1/ex1.ver
Executable file
|
@ -0,0 +1 @@
|
|||
1.0.2
|
0
resources/tests/valid_sqpack/game/sqpack/ex1/test.win32.dat0
Executable file
0
resources/tests/valid_sqpack/game/sqpack/ex1/test.win32.dat0
Executable file
1
resources/tests/valid_sqpack/game/sqpack/ex2/ex2.ver
Executable file
1
resources/tests/valid_sqpack/game/sqpack/ex2/ex2.ver
Executable file
|
@ -0,0 +1 @@
|
|||
1.0.3
|
0
resources/tests/valid_sqpack/game/sqpack/ex2/test.win32.dat0
Executable file
0
resources/tests/valid_sqpack/game/sqpack/ex2/test.win32.dat0
Executable file
0
resources/tests/valid_sqpack/game/sqpack/ffxiv/0c0100.win32.dat0
Executable file
0
resources/tests/valid_sqpack/game/sqpack/ffxiv/0c0100.win32.dat0
Executable file
145
src/blowfish.rs
Executable file
145
src/blowfish.rs
Executable file
|
@ -0,0 +1,145 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use crate::blowfish_constants::{BLOWFISH_P, BLOWFISH_S};
|
||||
|
||||
const ROUNDS: usize = 16;
|
||||
const KEYBITS: u32 = 64u32 >> 3;
|
||||
|
||||
/// Implementation of the Blowfish block cipher, specialized for encrypting and decrypting SqexArg.
|
||||
pub struct Blowfish {
|
||||
p: [u32; 18],
|
||||
s: [[u32; 256]; 4],
|
||||
}
|
||||
|
||||
impl Blowfish {
|
||||
pub fn new(key: &[u8]) -> Blowfish {
|
||||
let mut s = Self {
|
||||
p: BLOWFISH_P,
|
||||
s: BLOWFISH_S,
|
||||
};
|
||||
|
||||
let mut j = 0usize;
|
||||
for i in 0..ROUNDS + 2 {
|
||||
let mut data = 0u32;
|
||||
for _ in 0..4 {
|
||||
data = (data << 8) | (key[j as usize] as u32);
|
||||
j += 1;
|
||||
|
||||
if j >= (KEYBITS as usize) {
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
|
||||
s.p[i] ^= data;
|
||||
}
|
||||
|
||||
let mut l = 0u32;
|
||||
let mut r = 0u32;
|
||||
|
||||
for i in (0..18).step_by(2) {
|
||||
let (l_new, r_new) = s.encrypt_pair(l, r);
|
||||
s.p[i] = l_new;
|
||||
s.p[i + 1] = r_new;
|
||||
|
||||
l = l_new;
|
||||
r = r_new;
|
||||
}
|
||||
|
||||
for i in 0..4 {
|
||||
for j in (0..256).step_by(2) {
|
||||
let (l_new, r_new) = s.encrypt_pair(l, r);
|
||||
s.s[i][j] = l_new;
|
||||
s.s[i][j + 1] = r_new;
|
||||
|
||||
l = l_new;
|
||||
r = r_new;
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Encrypts a block of data. If the encryption for any reason fails, returns None.
|
||||
pub fn encrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
|
||||
let padded_data = Blowfish::pad_buffer(data);
|
||||
|
||||
let mut cursor = Cursor::new(Vec::with_capacity(padded_data.len()));
|
||||
|
||||
for i in (0..padded_data.len()).step_by(8) {
|
||||
let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
|
||||
let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
|
||||
|
||||
let (l, r) = self.encrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
|
||||
|
||||
cursor.write(u32::to_le_bytes(l).as_slice()).ok()?;
|
||||
cursor.write(u32::to_le_bytes(r).as_slice()).ok()?;
|
||||
}
|
||||
|
||||
Some(cursor.into_inner())
|
||||
}
|
||||
|
||||
fn pad_buffer(data: &[u8]) -> Vec<u8> {
|
||||
let mut padded_length = data.len();
|
||||
if data.len() % 8 != 0 {
|
||||
padded_length = data.len() + (8 - (data.len() % 8));
|
||||
}
|
||||
|
||||
let mut vec = Vec::with_capacity(padded_length);
|
||||
vec.resize(padded_length, 0);
|
||||
vec[..data.len()].clone_from_slice(data);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Decrypts a block of data. If the decryption fails due to buffer overflow issues, will return None - but it
|
||||
/// does not indicate that the wrong key was used.
|
||||
pub fn decrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
|
||||
let padded_data = Blowfish::pad_buffer(data);
|
||||
|
||||
let mut buffer = Vec::with_capacity(padded_data.len());
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
|
||||
for i in (0..padded_data.len()).step_by(8) {
|
||||
let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
|
||||
let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
|
||||
|
||||
let (l, r) = self.decrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
|
||||
|
||||
cursor.write(u32::to_le_bytes(l).as_slice()).ok()?;
|
||||
cursor.write(u32::to_le_bytes(r).as_slice()).ok()?;
|
||||
}
|
||||
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
/// Calculates the F-function for `x`.
|
||||
fn f(&self, x: u32) -> u32 {
|
||||
let a = self.s[0][(x >> 24) as usize];
|
||||
let b = self.s[1][((x >> 16) & 0xFF) as usize];
|
||||
let c = self.s[2][((x >> 8) & 0xFF) as usize];
|
||||
let d = self.s[3][(x & 0xFF) as usize];
|
||||
|
||||
(a.wrapping_add(b) ^ c).wrapping_add(d)
|
||||
}
|
||||
|
||||
fn encrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
|
||||
for i in (0..ROUNDS).step_by(2) {
|
||||
l ^= self.p[i];
|
||||
r ^= self.f(l);
|
||||
r ^= self.p[i + 1];
|
||||
l ^= self.f(r);
|
||||
}
|
||||
|
||||
return (r ^ self.p[17], l ^ self.p[16]);
|
||||
}
|
||||
|
||||
fn decrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
|
||||
for i in (2..ROUNDS + 1).step_by(2).rev() {
|
||||
l ^= self.p[i + 1];
|
||||
r ^= self.f(l);
|
||||
r ^= self.p[i];
|
||||
l ^= self.f(r);
|
||||
}
|
||||
|
||||
return (r ^ self.p[0], l ^ self.p[1]);
|
||||
}
|
||||
}
|
188
src/blowfish_constants.rs
Executable file
188
src/blowfish_constants.rs
Executable file
|
@ -0,0 +1,188 @@
|
|||
pub const BLOWFISH_P: [u32; 18] = [
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
|
||||
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
|
||||
];
|
||||
|
||||
pub const BLOWFISH_S: [[u32; 256]; 4] =
|
||||
[[
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
|
||||
0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
|
||||
0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
|
||||
0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
|
||||
0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
|
||||
0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
|
||||
0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
|
||||
0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
|
||||
0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
|
||||
0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
|
||||
0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
|
||||
0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
|
||||
0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
|
||||
0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
|
||||
0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
|
||||
0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
|
||||
0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
|
||||
0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
|
||||
0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
|
||||
0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
|
||||
0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
|
||||
0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
|
||||
],
|
||||
[
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
|
||||
0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
|
||||
0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
|
||||
0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
|
||||
0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
|
||||
0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
|
||||
0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
|
||||
0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
|
||||
0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
|
||||
0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
|
||||
0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
|
||||
0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
|
||||
0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
|
||||
0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
|
||||
0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
|
||||
0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
|
||||
0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
|
||||
0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
|
||||
0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
|
||||
0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
|
||||
0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
|
||||
0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
|
||||
],
|
||||
[
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
|
||||
0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
|
||||
0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
|
||||
0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
|
||||
0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
|
||||
0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
|
||||
0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
|
||||
0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
|
||||
0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
|
||||
0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
|
||||
0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
|
||||
0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
|
||||
0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
|
||||
0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
|
||||
0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
|
||||
0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
|
||||
0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
|
||||
0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
|
||||
0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
|
||||
0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
|
||||
0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
|
||||
0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
|
||||
],
|
||||
[
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
|
||||
0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
|
||||
0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
|
||||
0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
|
||||
0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
|
||||
0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
|
||||
0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
|
||||
0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
|
||||
0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
|
||||
0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
|
||||
0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
|
||||
0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
|
||||
0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
|
||||
0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
|
||||
0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
|
||||
0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
|
||||
0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
|
||||
0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
|
||||
0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
|
||||
0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
|
||||
0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
|
||||
0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||
]
|
||||
];
|
43
src/bootdata.rs
Executable file
43
src/bootdata.rs
Executable file
|
@ -0,0 +1,43 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Boot data for FFXIV.
|
||||
pub struct BootData {
|
||||
version: String,
|
||||
}
|
||||
|
||||
fn is_valid(path: &str) -> bool {
|
||||
let d = PathBuf::from(path);
|
||||
|
||||
if fs::metadata(d.as_path()).is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
impl BootData {
|
||||
/// Reads from an existing boot data location.
|
||||
///
|
||||
/// This will return _None_ if the boot directory is not valid, but it does not check the validity
|
||||
/// of each individual file.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use physis::bootdata::BootData;
|
||||
/// let boot = BootData::from_existing("SquareEnix/Final Fantasy XIV - A Realm Reborn/boot");
|
||||
/// # assert!(boot.is_none())
|
||||
/// ```
|
||||
pub fn from_existing(directory: &str) -> Option<BootData> {
|
||||
match is_valid(directory) {
|
||||
true => Some(BootData {
|
||||
version: String::new()
|
||||
}),
|
||||
false => {
|
||||
println!("Boot data is not valid!");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
src/common.rs
Executable file
24
src/common.rs
Executable file
|
@ -0,0 +1,24 @@
|
|||
#[repr(u8)]
|
||||
pub enum Language {
|
||||
None,
|
||||
Japanese,
|
||||
English,
|
||||
German,
|
||||
French,
|
||||
ChineseSimplified,
|
||||
ChineseTraditional,
|
||||
Korean,
|
||||
}
|
||||
|
||||
pub fn get_language_code(lang: Language) -> &'static str {
|
||||
match lang {
|
||||
Language::None => "",
|
||||
Language::Japanese => "ja",
|
||||
Language::English => "en",
|
||||
Language::German => "de",
|
||||
Language::French => "fr",
|
||||
Language::ChineseSimplified => "chs",
|
||||
Language::ChineseTraditional => "cht",
|
||||
Language::Korean => "ko"
|
||||
}
|
||||
}
|
102
src/compression.rs
Executable file
102
src/compression.rs
Executable file
|
@ -0,0 +1,102 @@
|
|||
use std::ptr::null_mut;
|
||||
use libz_sys::*;
|
||||
|
||||
// This module's functions are licensed under MIT from https://github.com/rust-lang/flate2-rs
|
||||
mod flate2_zallocation {
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::null_mut;
|
||||
use std::alloc::{self, Layout};
|
||||
|
||||
const ALIGN: usize = std::mem::align_of::<usize>();
|
||||
|
||||
fn align_up(size: usize, align: usize) -> usize {
|
||||
(size + align - 1) & !(align - 1)
|
||||
}
|
||||
|
||||
pub extern "C" fn zalloc(_ptr: *mut c_void, items: u32, item_size: u32) -> *mut c_void {
|
||||
// We need to multiply `items` and `item_size` to get the actual desired
|
||||
// allocation size. Since `zfree` doesn't receive a size argument we
|
||||
// also need to allocate space for a `usize` as a header so we can store
|
||||
// how large the allocation is to deallocate later.
|
||||
let size = match items
|
||||
.checked_mul(item_size)
|
||||
.and_then(|i| usize::try_from(i).ok())
|
||||
.map(|size| align_up(size, ALIGN))
|
||||
.and_then(|i| i.checked_add(std::mem::size_of::<usize>()))
|
||||
{
|
||||
Some(i) => i,
|
||||
None => return null_mut(),
|
||||
};
|
||||
|
||||
// Make sure the `size` isn't too big to fail `Layout`'s restrictions
|
||||
let layout = match Layout::from_size_align(size, ALIGN) {
|
||||
Ok(layout) => layout,
|
||||
Err(_) => return null_mut(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
// Allocate the data, and if successful store the size we allocated
|
||||
// at the beginning and then return an offset pointer.
|
||||
let ptr = alloc::alloc(layout) as *mut usize;
|
||||
if ptr.is_null() {
|
||||
return ptr as *mut c_void;
|
||||
}
|
||||
*ptr = size;
|
||||
ptr.add(1) as *mut c_void
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "C" fn zfree(_ptr: *mut c_void, address: *mut c_void) {
|
||||
unsafe {
|
||||
// Move our address being freed back one pointer, read the size we
|
||||
// stored in `zalloc`, and then free it using the standard Rust
|
||||
// allocator.
|
||||
let ptr = (address as *mut usize).offset(-1);
|
||||
let size = *ptr;
|
||||
let layout = Layout::from_size_align_unchecked(size, ALIGN);
|
||||
alloc::dealloc(ptr as *mut u8, layout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_header_decompress(in_data: &mut [u8], out_data: &mut [u8]) -> bool {
|
||||
use crate::compression::flate2_zallocation::zalloc;
|
||||
use crate::compression::flate2_zallocation::zfree;
|
||||
|
||||
unsafe {
|
||||
let mut strm = z_stream {
|
||||
next_in: null_mut(),
|
||||
avail_in: in_data.len() as u32,
|
||||
total_in: 0,
|
||||
next_out: null_mut(),
|
||||
avail_out: 0,
|
||||
total_out: 0,
|
||||
msg: null_mut(),
|
||||
state: null_mut(),
|
||||
zalloc,
|
||||
zfree,
|
||||
opaque: null_mut(),
|
||||
data_type: 0,
|
||||
adler: 0,
|
||||
reserved: 0,
|
||||
};
|
||||
|
||||
let ret = inflateInit2_(&mut strm, -15, zlibVersion(), core::mem::size_of::<z_stream>() as i32);
|
||||
if ret != Z_OK {
|
||||
return false;
|
||||
}
|
||||
|
||||
strm.next_in = in_data.as_mut_ptr();
|
||||
strm.avail_out = out_data.len() as u32;
|
||||
strm.next_out = out_data.as_mut_ptr();
|
||||
|
||||
let ret = inflate(&mut strm, Z_NO_FLUSH);
|
||||
if ret != Z_STREAM_END {
|
||||
return false;
|
||||
}
|
||||
|
||||
inflateEnd(&mut strm);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
333
src/dat.rs
Executable file
333
src/dat.rs
Executable file
|
@ -0,0 +1,333 @@
|
|||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use binrw::BinRead;
|
||||
use binrw::binrw;
|
||||
use crate::compression::no_header_decompress;
|
||||
use crate::gamedata::MemoryBuffer;
|
||||
use crate::model::ModelHeader;
|
||||
use std::io::Write;
|
||||
use binrw::BinWrite;
|
||||
use crate::sqpack::read_data_block;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FileType {
|
||||
Empty = 1,
|
||||
Standard,
|
||||
Model,
|
||||
Texture,
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct StandardFileBlock {
|
||||
#[br(pad_before = 8)]
|
||||
num_blocks: u32,
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct TextureLodBlock {
|
||||
compressed_offset: u32,
|
||||
compressed_size: u32,
|
||||
decompressed_size: u32,
|
||||
|
||||
block_offset: u32,
|
||||
block_count: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
pub struct ModelMemorySizes<T: 'static + binrw::BinRead<Args=()> + binrw::BinWrite<Args=()> + Default + std::ops::AddAssign + Copy> {
|
||||
pub stack_size: T,
|
||||
pub runtime_size: T,
|
||||
|
||||
pub vertex_buffer_size: [T; 3],
|
||||
pub edge_geometry_vertex_buffer_size: [T; 3],
|
||||
pub index_buffer_size: [T; 3],
|
||||
}
|
||||
|
||||
impl<T: 'static + binrw::BinRead<Args=()> + binrw::BinWrite<Args=()> + Default + std::ops::AddAssign + Copy> ModelMemorySizes<T> {
|
||||
pub fn total(&self) -> T {
|
||||
let mut total: T = T::default();
|
||||
|
||||
total += self.stack_size;
|
||||
total += self.runtime_size;
|
||||
|
||||
for i in 0..3 {
|
||||
total += self.vertex_buffer_size[i];
|
||||
total += self.edge_geometry_vertex_buffer_size[i];
|
||||
total += self.index_buffer_size[i];
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct ModelFileBlock {
|
||||
pub num_blocks: u32,
|
||||
pub num_used_blocks: u32,
|
||||
pub version: u32,
|
||||
|
||||
pub uncompressed_size: ModelMemorySizes<u32>,
|
||||
pub compressed_size: ModelMemorySizes<u32>,
|
||||
pub offset: ModelMemorySizes<u32>,
|
||||
pub index: ModelMemorySizes<u16>,
|
||||
pub num: ModelMemorySizes<u16>,
|
||||
|
||||
pub vertex_declaration_num: u16,
|
||||
pub material_num: u16,
|
||||
pub num_lods: u8,
|
||||
|
||||
#[br(map = | x: u8 | x != 0)]
|
||||
pub index_buffer_streaming_enabled: bool,
|
||||
#[brw(pad_after = 1)]
|
||||
#[br(map = | x: u8 | x != 0)]
|
||||
pub edge_geometry_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct TextureBlock {
|
||||
#[br(pad_before = 8)]
|
||||
num_blocks: u32,
|
||||
|
||||
#[br(count = num_blocks)]
|
||||
lods: Vec<TextureLodBlock>,
|
||||
}
|
||||
|
||||
/// A SqPack file info header. It can optionally contain extra information, such as texture or
|
||||
/// model data depending on the file type.
|
||||
#[derive(BinRead)]
|
||||
struct FileInfo {
|
||||
size: u32,
|
||||
file_type: FileType,
|
||||
file_size: i32,
|
||||
|
||||
#[br(if (file_type == FileType::Standard))]
|
||||
standard_info: Option<StandardFileBlock>,
|
||||
|
||||
#[br(if (file_type == FileType::Model))]
|
||||
model_info: Option<ModelFileBlock>,
|
||||
|
||||
#[br(if (file_type == FileType::Texture))]
|
||||
texture_info: Option<TextureBlock>,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
pub struct Block {
|
||||
#[br(pad_after = 4)]
|
||||
offset: i32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug)]
|
||||
#[br(import { x : i32, y : i32 })]
|
||||
#[br(map = | _ : i32 | if x < 32000 { CompressionMode::Compressed{ compressed_length : x, decompressed_length : y} } else { CompressionMode::Uncompressed { file_size : y } } )]
|
||||
pub enum CompressionMode {
|
||||
// we manually map here, because for this case the enum value is also a raw value we want to extract :-)
|
||||
Compressed { compressed_length: i32, decompressed_length: i32 },
|
||||
Uncompressed { file_size: i32 },
|
||||
}
|
||||
|
||||
#[binrw::binread]
|
||||
#[derive(Debug)]
|
||||
pub struct BlockHeader {
|
||||
#[br(pad_after = 4)]
|
||||
pub size : u32,
|
||||
|
||||
#[br(temp)]
|
||||
x : i32,
|
||||
|
||||
#[br(temp)]
|
||||
y : i32,
|
||||
|
||||
#[br(args { x, y })]
|
||||
#[br(restore_position)]
|
||||
pub compression: CompressionMode,
|
||||
}
|
||||
|
||||
pub struct DatFile {
|
||||
file: std::fs::File,
|
||||
}
|
||||
|
||||
// from https://users.rust-lang.org/t/how-best-to-convert-u8-to-u16/57551/4
|
||||
fn to_u8_slice(slice: &mut [u16]) -> &mut [u8] {
|
||||
let byte_len = 2 * slice.len();
|
||||
unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
slice.as_mut_ptr().cast::<u8>(),
|
||||
byte_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DatFile {
|
||||
/// Creates a new reference to an existing dat file.
|
||||
pub fn from_existing(path: &str) -> Option<DatFile> {
|
||||
Some(DatFile {
|
||||
file: std::fs::File::open(path).ok()?
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn read_from_offset(&mut self, offset: u32) -> Option<MemoryBuffer> {
|
||||
let offset: u64 = (offset * 0x80) as u64;
|
||||
|
||||
self.file.seek(SeekFrom::Start(offset))
|
||||
.expect("TODO: panic message");
|
||||
|
||||
let file_info = FileInfo::read(&mut self.file)
|
||||
.expect("Failed to parse file info.");
|
||||
|
||||
match file_info.file_type {
|
||||
FileType::Empty => None,
|
||||
FileType::Standard => {
|
||||
let standard_file_info = file_info.standard_info.unwrap();
|
||||
|
||||
let mut blocks: Vec<Block> = Vec::with_capacity(standard_file_info.num_blocks as usize);
|
||||
|
||||
for _ in 0..standard_file_info.num_blocks {
|
||||
blocks.push(Block::read(&mut self.file).unwrap());
|
||||
}
|
||||
|
||||
let mut data: Vec<u8> = Vec::with_capacity(file_info.file_size as usize);
|
||||
|
||||
let starting_position = offset + (file_info.size as u64);
|
||||
|
||||
for i in 0..standard_file_info.num_blocks {
|
||||
data.append(&mut read_data_block(&mut self.file, starting_position + (blocks[i as usize].offset as u64))
|
||||
.expect("Failed to read data block."));
|
||||
}
|
||||
|
||||
Some(data)
|
||||
}
|
||||
FileType::Model => {
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
|
||||
let model_file_info = file_info.model_info.unwrap();
|
||||
|
||||
let base_offset = offset + (file_info.size as u64);
|
||||
|
||||
let total_blocks = model_file_info.num.total();
|
||||
|
||||
let mut compressed_block_sizes: Vec<u16> = vec![0; total_blocks as usize];
|
||||
let slice: &mut [u8] = to_u8_slice(&mut compressed_block_sizes);
|
||||
|
||||
self.file.read_exact(slice).ok()?;
|
||||
|
||||
let mut current_block = 0;
|
||||
|
||||
let mut vertex_data_offsets: [u32; 3] = [0; 3];
|
||||
let mut vertex_data_sizes: [u32; 3] = [0; 3];
|
||||
|
||||
let mut index_data_offsets: [u32; 3] = [0; 3];
|
||||
let mut index_data_sizes: [u32; 3] = [0; 3];
|
||||
|
||||
// start writing at 0x44
|
||||
buffer.seek(SeekFrom::Start(0x44)).ok()?;
|
||||
|
||||
self.file.seek(SeekFrom::Start(base_offset + (model_file_info.offset.stack_size as u64))).ok()?;
|
||||
|
||||
// read from stack blocks
|
||||
let mut read_model_blocks = |offset: u64, size: usize| -> Option<u64> {
|
||||
self.file.seek(SeekFrom::Start(base_offset + offset)).ok()?;
|
||||
let stack_start = buffer.position();
|
||||
for _ in 0..size {
|
||||
let last_pos = &self.file.stream_position().unwrap();
|
||||
|
||||
let data = read_data_block(&self.file, *last_pos)
|
||||
.expect("Unable to read block data.");
|
||||
// write to buffer
|
||||
buffer.write(data.as_slice()).ok()?;
|
||||
|
||||
self.file.seek(SeekFrom::Start(last_pos + (compressed_block_sizes[current_block as usize] as u64))).ok()?;
|
||||
current_block += 1;
|
||||
}
|
||||
|
||||
Some(buffer.position() - stack_start)
|
||||
};
|
||||
|
||||
let stack_size = read_model_blocks(model_file_info.offset.stack_size as u64, model_file_info.num.stack_size as usize).unwrap() as u32;
|
||||
let runtime_size = read_model_blocks(model_file_info.offset.runtime_size as u64, model_file_info.num.runtime_size as usize).unwrap() as u32;
|
||||
|
||||
let mut process_model_data = |i: usize, size: u32, offset: u32, offsets: &mut [u32; 3], data_sizes: &mut [u32; 3]| {
|
||||
if size != 0 {
|
||||
let current_vertex_offset = buffer.position() as u32;
|
||||
if i == 0 || current_vertex_offset != offsets[i - 1] {
|
||||
offsets[i] = current_vertex_offset;
|
||||
} else {
|
||||
offsets[i] = 0;
|
||||
}
|
||||
|
||||
self.file.seek(SeekFrom::Start(base_offset + (offset as u64))).ok();
|
||||
|
||||
for _ in 0..size {
|
||||
let last_pos = self.file.stream_position().unwrap();
|
||||
|
||||
let data = read_data_block(&self.file, last_pos)
|
||||
.expect("Unable to read raw model block!");
|
||||
|
||||
buffer.write(data.as_slice()).expect("Unable to write to memory buffer!");
|
||||
|
||||
data_sizes[i] += data.len() as u32;
|
||||
self.file.seek(SeekFrom::Start(last_pos + (compressed_block_sizes[current_block] as u64)))
|
||||
.expect("Unable to seek properly.");
|
||||
current_block += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// process all 3 lods
|
||||
for i in 0..3 {
|
||||
// process vertices
|
||||
process_model_data(i, model_file_info.num.vertex_buffer_size[i] as u32, model_file_info.offset.vertex_buffer_size[i], &mut vertex_data_offsets, &mut vertex_data_sizes);
|
||||
|
||||
// TODO: process edges
|
||||
|
||||
// process indices
|
||||
process_model_data(i, model_file_info.num.index_buffer_size[i] as u32, model_file_info.offset.index_buffer_size[i], &mut index_data_offsets, &mut index_data_sizes);
|
||||
}
|
||||
|
||||
let header = ModelHeader {
|
||||
version: model_file_info.version,
|
||||
stack_size,
|
||||
runtime_size,
|
||||
vertex_declaration_count: model_file_info.vertex_declaration_num,
|
||||
material_count: model_file_info.material_num,
|
||||
vertex_offsets: vertex_data_offsets,
|
||||
index_offsets: index_data_offsets,
|
||||
vertex_buffer_size: vertex_data_sizes,
|
||||
index_buffer_size: index_data_sizes,
|
||||
lod_count: model_file_info.num_lods,
|
||||
index_buffer_streaming_enabled: model_file_info.index_buffer_streaming_enabled,
|
||||
has_edge_geometry: model_file_info.edge_geometry_enabled,
|
||||
};
|
||||
|
||||
buffer.seek(SeekFrom::Start(0)).ok()?;
|
||||
|
||||
header.write_to(&mut buffer).ok()?;
|
||||
|
||||
Some(buffer.into_inner())
|
||||
}
|
||||
FileType::Texture => {
|
||||
let mut data: Vec<u8> = Vec::with_capacity(file_info.file_size as usize);
|
||||
|
||||
let texture_file_info = file_info.texture_info.unwrap();
|
||||
|
||||
for i in 0..texture_file_info.num_blocks {
|
||||
let mut running_block_total = (texture_file_info.lods[i as usize].compressed_offset as u64) + offset + (file_info.size as u64);
|
||||
|
||||
data.append(&mut read_data_block(&self.file, running_block_total).unwrap());
|
||||
|
||||
for _ in 1..texture_file_info.lods[i as usize].block_count {
|
||||
running_block_total += i16::read(&mut self.file).unwrap() as u64;
|
||||
data.append(&mut read_data_block(&self.file, running_block_total).unwrap());
|
||||
}
|
||||
|
||||
// dummy?
|
||||
i16::read(&mut self.file).unwrap();
|
||||
}
|
||||
|
||||
Some(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
src/equipment.rs
Executable file
61
src/equipment.rs
Executable file
|
@ -0,0 +1,61 @@
|
|||
use crate::race::{Gender, get_race_id, Race, Subrace};
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum Slot {
|
||||
Head,
|
||||
Hands,
|
||||
Legs,
|
||||
Feet,
|
||||
Body,
|
||||
Earring,
|
||||
Neck,
|
||||
Rings,
|
||||
Wrists,
|
||||
}
|
||||
|
||||
pub fn get_slot_abbreviation(slot: Slot) -> &'static str {
|
||||
match slot {
|
||||
Slot::Head => "met",
|
||||
Slot::Hands => "glv",
|
||||
Slot::Legs => "dwn",
|
||||
Slot::Feet => "sho",
|
||||
Slot::Body => "top",
|
||||
Slot::Earring => "ear",
|
||||
Slot::Neck => "nek",
|
||||
Slot::Rings => "rir",
|
||||
Slot::Wrists => "wrs"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_slot_from_id(id: i32) -> Option<Slot> {
|
||||
match id {
|
||||
3 => Some(Slot::Head),
|
||||
5 => Some(Slot::Hands),
|
||||
7 => Some(Slot::Legs),
|
||||
8 => Some(Slot::Feet),
|
||||
4 => Some(Slot::Body),
|
||||
9 => Some(Slot::Earring),
|
||||
10 => Some(Slot::Neck),
|
||||
12 => Some(Slot::Rings),
|
||||
11 => Some(Slot::Wrists),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_equipment_path(model_id: i32, race: Race, subrace: Subrace, gender: Gender, slot: Slot) -> String {
|
||||
format!("chara/equipment/e{:04}/model/c{:04}e{:04}_{}.mdl",
|
||||
model_id,
|
||||
get_race_id(race, subrace, gender).unwrap(),
|
||||
model_id,
|
||||
get_slot_abbreviation(slot))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_equipment_path() {
|
||||
assert_eq!(build_equipment_path(0, Race::Hyur, Subrace::Midlander, Gender::Male, Slot::Body), "chara/equipment/e0000/model/c0101e0000_top.mdl");
|
||||
}
|
||||
}
|
86
src/exl.rs
Executable file
86
src/exl.rs
Executable file
|
@ -0,0 +1,86 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
/// Represents an Excel List.
|
||||
pub struct EXL {
|
||||
/// The version of the list.
|
||||
pub version: i32,
|
||||
|
||||
/// The entries of the list.
|
||||
pub entries: HashMap<String, i32>,
|
||||
}
|
||||
|
||||
impl EXL {
|
||||
/// Initializes `EXL` from an existing list.
|
||||
pub fn from_existing(path: &str) -> Option<EXL> {
|
||||
let mut exl = Self {
|
||||
version: 0,
|
||||
entries: HashMap::new(),
|
||||
};
|
||||
|
||||
let file = std::fs::File::open(path).unwrap();
|
||||
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for (_, line) in reader.lines().enumerate() {
|
||||
// now parse the line!
|
||||
|
||||
let unwrap = line.unwrap();
|
||||
let (name, value) = unwrap.split_once(',').unwrap();
|
||||
|
||||
let parsed_value: i32 = value.parse().unwrap();
|
||||
|
||||
if name == "EXLT" {
|
||||
exl.version = parsed_value;
|
||||
} else {
|
||||
exl.entries.insert(name.parse().unwrap(), parsed_value);
|
||||
}
|
||||
}
|
||||
|
||||
Some(exl)
|
||||
}
|
||||
|
||||
/// Checks whether or not the list contains a key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use physis::exl::EXL;
|
||||
/// let exl = EXL::from_existing("root.exl").unwrap();
|
||||
/// exl.contains("Achivements");
|
||||
/// ```
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.entries.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use super::*;
|
||||
|
||||
fn common_setup() -> EXL {
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.push("resources/tests");
|
||||
d.push("test.exl");
|
||||
|
||||
EXL::from_existing(d.to_str().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_parsing() {
|
||||
let exl = common_setup();
|
||||
|
||||
assert_eq!(exl.version, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let exl = common_setup();
|
||||
|
||||
assert!(exl.contains("Foo"));
|
||||
|
||||
// should be case-sensitive
|
||||
assert!(!exl.contains("foo"));
|
||||
}
|
||||
}
|
212
src/gamedata.rs
Executable file
212
src/gamedata.rs
Executable file
|
@ -0,0 +1,212 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::fs::DirEntry;
|
||||
use std::path::PathBuf;
|
||||
use crate::dat::DatFile;
|
||||
use crate::index::IndexFile;
|
||||
use crate::repository::{Category, Repository, string_to_category};
|
||||
use crate::sqpack::calculate_hash;
|
||||
|
||||
/// Framework for operating on game data.
|
||||
pub struct GameData {
|
||||
/// The game directory to operate on.
|
||||
pub game_directory: String,
|
||||
|
||||
/// Repositories in the game directory.
|
||||
pub repositories: Vec<Repository>,
|
||||
}
|
||||
|
||||
fn is_valid(path: &str) -> bool {
|
||||
let mut d = PathBuf::from(path);
|
||||
|
||||
if fs::metadata(d.as_path()).is_err() {
|
||||
println!("Failed game directory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub type MemoryBuffer = Vec<u8>;
|
||||
|
||||
impl GameData {
|
||||
/// Read game data from an existing game installation.
|
||||
///
|
||||
/// This will return _None_ if the game directory is not valid, but it does not check the validity
|
||||
/// of each individual file.
|
||||
///
|
||||
/// **Note**: None of the repositories are searched, and it's required to call `reload_repositories()`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use physis::gamedata::GameData;
|
||||
/// GameData::from_existing("$FFXIV/game");
|
||||
/// ```
|
||||
pub fn from_existing(directory: &str) -> Option<GameData> {
|
||||
match is_valid(directory) {
|
||||
true => Some(Self {
|
||||
game_directory: String::from(directory),
|
||||
repositories: vec![],
|
||||
}),
|
||||
false => {
|
||||
println!("Game data is not valid!");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reloads all repository information from disk. This is a fast operation, as it's not actually
|
||||
/// reading any dat files yet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use physis::gamedata::GameData;
|
||||
/// let mut game = GameData::from_existing("$FFXIV/game").unwrap();
|
||||
/// game.reload_repositories();
|
||||
/// ```
|
||||
pub fn reload_repositories(&mut self) {
|
||||
self.repositories.clear();
|
||||
|
||||
let mut d = PathBuf::from(self.game_directory.as_str());
|
||||
d.push("sqpack");
|
||||
|
||||
let repository_paths: Vec<DirEntry> = fs::read_dir(d.as_path())
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|s| s.file_type().unwrap().is_dir())
|
||||
.collect();
|
||||
|
||||
for repository_path in repository_paths {
|
||||
self.repositories.push(Repository::from_existing(repository_path.path().to_str().unwrap()).unwrap());
|
||||
}
|
||||
|
||||
self.repositories.sort();
|
||||
}
|
||||
|
||||
fn get_index_file(&self, path: &str) -> Option<IndexFile> {
|
||||
let (repository, category) = self.parse_repository_category(path).unwrap();
|
||||
|
||||
let index_path = format!("{}/sqpack/{}/{}",
|
||||
self.game_directory, repository.name, repository.index_filename(category));
|
||||
|
||||
IndexFile::from_existing(index_path.as_str())
|
||||
}
|
||||
|
||||
/// Checks if a file located at `path` exists.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use physis::gamedata::GameData;
|
||||
/// # let mut game = GameData::from_existing("SquareEnix/Final Fantasy XIV - A Realm Reborn/game").unwrap();
|
||||
/// if game.exists("exd/cid.exl") {
|
||||
/// println!("Cid really does exist!");
|
||||
/// } else {
|
||||
/// println!("Oh noes!");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn exists(&self, path: &str) -> bool {
|
||||
let hash = calculate_hash(path);
|
||||
|
||||
let index_file = self.get_index_file(path)
|
||||
.expect("Failed to find index file.");
|
||||
|
||||
index_file.entries.iter().any(|s| s.hash == hash)
|
||||
}
|
||||
|
||||
/// Extracts the file lcoated at `path`. This is returned as an in-memory buffer, and will usually
|
||||
/// have to be further parsed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use physis::gamedata::GameData;
|
||||
/// # use std::io::Write;
|
||||
/// # let mut game = GameData::from_existing("SquareEnix/Final Fantasy XIV - A Realm Reborn/game").unwrap();
|
||||
/// let data = game.extract("exd/root.exl").unwrap();
|
||||
///
|
||||
/// let mut file = std::fs::File::create("root.exl").unwrap();
|
||||
/// file.write(data.as_slice());
|
||||
/// ```
|
||||
pub fn extract(&self, path: &str) -> Option<MemoryBuffer> {
|
||||
let hash = calculate_hash(path);
|
||||
|
||||
let index_file = self.get_index_file(path)
|
||||
.expect("Failed to find index file.");
|
||||
|
||||
let slice = index_file.entries.iter().find(|s| s.hash == hash);
|
||||
match slice {
|
||||
Some(entry) => {
|
||||
let (repository, category) = self.parse_repository_category(path).unwrap();
|
||||
|
||||
let dat_filename = repository.dat_filename(category, entry.bitfield.data_file_id().into());
|
||||
|
||||
let dat_path = format!("{}/sqpack/{}/{}",
|
||||
self.game_directory, repository.name, dat_filename);
|
||||
|
||||
let mut dat_file = DatFile::from_existing(dat_path.as_str()).unwrap();
|
||||
|
||||
dat_file.read_from_offset(entry.bitfield.offset())
|
||||
}
|
||||
None => {
|
||||
println!("Entry not found.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a path structure and spits out the corresponding category and repository.
|
||||
fn parse_repository_category(&self, path: &str) -> Option<(&Repository, Category)> {
|
||||
let tokens: Vec<&str> = path.split('/').collect(); // TODO: use split_once here
|
||||
let repository_token = tokens[0];
|
||||
|
||||
if tokens.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
for repository in &self.repositories {
|
||||
if repository.name == repository_token {
|
||||
return Some((repository, string_to_category(tokens[1])?));
|
||||
}
|
||||
}
|
||||
|
||||
Some((&self.repositories[0], string_to_category(tokens[0])?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::repository::Category::EXD;
|
||||
use super::*;
|
||||
|
||||
fn common_setup_data() -> GameData {
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.push("resources/tests");
|
||||
d.push("valid_sqpack");
|
||||
d.push("game");
|
||||
|
||||
GameData::from_existing(d.to_str().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repository_ordering() {
|
||||
let mut data = common_setup_data();
|
||||
data.reload_repositories();
|
||||
|
||||
assert_eq!(data.repositories[0].name, "ffxiv");
|
||||
assert_eq!(data.repositories[1].name, "ex1");
|
||||
assert_eq!(data.repositories[2].name, "ex2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repository_and_category_parsing() {
|
||||
let mut data = common_setup_data();
|
||||
data.reload_repositories();
|
||||
|
||||
assert_eq!(data.parse_repository_category("exd/root.exl").unwrap(),
|
||||
(&data.repositories[0], EXD));
|
||||
assert!(data.parse_repository_category("what/some_font.dat").is_none());
|
||||
}
|
||||
}
|
82
src/index.rs
Executable file
82
src/index.rs
Executable file
|
@ -0,0 +1,82 @@
|
|||
use std::io::SeekFrom;
|
||||
use binrw::binrw;
|
||||
use binrw::BinRead;
|
||||
use bitfield_struct::bitfield;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = u8)]
|
||||
#[derive(Debug)]
|
||||
pub enum PlatformId {
|
||||
Win32,
|
||||
PS3,
|
||||
PS4,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[br(magic = b"SqPack")]
|
||||
pub struct SqPackHeader {
|
||||
#[br(pad_before = 2)]
|
||||
platform_id: PlatformId,
|
||||
#[br(pad_before = 3)]
|
||||
size: u32,
|
||||
version: u32,
|
||||
file_type: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
pub struct SqPackIndexHeader {
|
||||
size: u32,
|
||||
file_type: u32,
|
||||
index_data_offset: u32,
|
||||
index_data_size: u32,
|
||||
}
|
||||
|
||||
#[bitfield(u32)]
|
||||
#[binrw]
|
||||
#[br(map = | x: u32 | Self::from(x))]
|
||||
pub struct IndexHashBitfield {
|
||||
#[bits(1)]
|
||||
pub size: u32,
|
||||
|
||||
#[bits(3)]
|
||||
pub data_file_id: u32,
|
||||
|
||||
#[bits(28)]
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
pub struct IndexHashTableEntry {
|
||||
pub(crate) hash: u64,
|
||||
#[br(pad_after = 4)]
|
||||
pub(crate) bitfield: IndexHashBitfield,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IndexEntry {
|
||||
pub hash: u64,
|
||||
pub data_file_id: u8,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
pub struct IndexFile {
|
||||
sqpack_header: SqPackHeader,
|
||||
|
||||
#[br(seek_before = SeekFrom::Start(sqpack_header.size.into()))]
|
||||
index_header: SqPackIndexHeader,
|
||||
|
||||
#[br(seek_before = SeekFrom::Start(index_header.index_data_offset.into()))]
|
||||
#[br(count = index_header.index_data_size / (core::mem::size_of::< IndexHashTableEntry > () as u32))]
|
||||
pub entries: Vec<IndexHashTableEntry>,
|
||||
}
|
||||
|
||||
impl IndexFile {
|
||||
/// Creates a new reference to an existing index file, this reads both an index and index2 file.
|
||||
pub fn from_existing(path: &str) -> Option<IndexFile> {
|
||||
let mut index_file = std::fs::File::open(path)
|
||||
.expect("Failed to read index file.");
|
||||
|
||||
Some(IndexFile::read(&mut index_file).ok()?)
|
||||
}
|
||||
}
|
34
src/lib.rs
Executable file
34
src/lib.rs
Executable file
|
@ -0,0 +1,34 @@
|
|||
extern crate core;
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use crate::bootdata::BootData;
|
||||
use crate::gamedata::GameData;
|
||||
|
||||
pub mod gamedata;
|
||||
|
||||
/// Reading game data repositories, such as "ffxiv" and "ex1", and so on.
|
||||
pub mod repository;
|
||||
|
||||
pub mod bootdata;
|
||||
|
||||
/// Everything to do with reading SqPack files.
|
||||
pub mod sqpack;
|
||||
|
||||
pub mod index;
|
||||
pub mod dat;
|
||||
mod compression;
|
||||
mod model;
|
||||
pub mod race;
|
||||
|
||||
/// Reading Excel lists (EXL).
|
||||
pub mod exl;
|
||||
pub mod equipment;
|
||||
pub mod common;
|
||||
pub mod patch;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod blowfish;
|
||||
mod blowfish_constants;
|
33
src/macros.rs
Executable file
33
src/macros.rs
Executable file
|
@ -0,0 +1,33 @@
|
|||
/// Creates a enum list of combined race identifiers.
|
||||
#[macro_export] macro_rules! define_race_enum {
|
||||
(
|
||||
pub enum $name:ident {
|
||||
$(
|
||||
$([$id:expr]($race:ident, $gender:ident $(, $subrace:ident)?))*
|
||||
),+$(,)?
|
||||
}
|
||||
) => {
|
||||
paste! {
|
||||
#[derive(PartialEq, Debug)]
|
||||
|
||||
pub enum $name {
|
||||
$(
|
||||
$([<$race $($subrace)? $gender>] = $id)*
|
||||
,
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
paste! {
|
||||
pub fn convert_to_internal(race : Race, subrace : Subrace, gender : Gender) -> Option<$name> {
|
||||
$(
|
||||
$(if race == $race $(&& subrace == $subrace)? && gender == $gender {
|
||||
return Some($name::[<$race $($subrace)? $gender>])
|
||||
})*
|
||||
)+
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
33
src/model.rs
Executable file
33
src/model.rs
Executable file
|
@ -0,0 +1,33 @@
|
|||
use binrw::binrw;
|
||||
|
||||
#[binrw]
|
||||
pub struct ModelHeader {
|
||||
pub(crate) version: u32,
|
||||
|
||||
pub stack_size: u32,
|
||||
pub runtime_size: u32,
|
||||
|
||||
pub vertex_declaration_count: u16,
|
||||
pub material_count: u16,
|
||||
|
||||
pub vertex_offsets: [u32; 3],
|
||||
pub index_offsets: [u32; 3],
|
||||
pub vertex_buffer_size: [u32; 3],
|
||||
pub index_buffer_size: [u32; 3],
|
||||
|
||||
pub lod_count: u8,
|
||||
|
||||
#[br(map = | x: u8 | x != 0)]
|
||||
#[bw(map = | x: & bool | -> u8 { if * x { 1 } else { 0 } })]
|
||||
pub index_buffer_streaming_enabled: bool,
|
||||
#[br(map = | x: u8 | x != 0)]
|
||||
#[bw(map = | x: & bool | -> u8 { if * x { 1 } else { 0 } })]
|
||||
#[brw(pad_after = 1)]
|
||||
pub has_edge_geometry: bool,
|
||||
}
|
||||
|
||||
pub struct Model {}
|
||||
|
||||
impl Model {
|
||||
pub fn from_existing() {}
|
||||
}
|
444
src/patch.rs
Executable file
444
src/patch.rs
Executable file
|
@ -0,0 +1,444 @@
|
|||
use std::fs;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use binrw::BinRead;
|
||||
use binrw::binread;
|
||||
use crate::{BootData, GameData};
|
||||
use crate::sqpack::{read_data_block, read_data_block_patch};
|
||||
use core::cmp::min;
|
||||
use crate::patch::TargetHeaderKind::Version;
|
||||
|
||||
#[derive(BinRead, Debug)]
|
||||
struct PatchChunk {
|
||||
#[br(big)]
|
||||
size: u32,
|
||||
chunk_type: ChunkType,
|
||||
#[br(if(chunk_type != ChunkType::EndOfFile))]
|
||||
crc32: u32
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
enum ChunkType {
|
||||
#[br(magic = b"FHDR")] FileHeader(FileHeaderChunk),
|
||||
#[br(magic = b"APLY")] ApplyOption(ApplyOptionChunk),
|
||||
#[br(magic = b"ADIR")] AddDirectory(AddDirectoryChunk),
|
||||
#[br(magic = b"DELD")] DeleteDirectory(DeleteDirectoryChunk),
|
||||
#[br(magic = b"SQPK")] Sqpk(SqpkChunk),
|
||||
#[br(magic = b"EOF_")] EndOfFile,
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct FileHeaderChunk {
|
||||
#[br(pad_before = 2)]
|
||||
#[br(pad_after = 1)]
|
||||
version: u8,
|
||||
|
||||
#[br(count = 4)]
|
||||
#[br(map = | x: Vec < u8 > | String::from_utf8(x).unwrap())]
|
||||
name: String,
|
||||
|
||||
#[br(big)]
|
||||
entry_files: u32,
|
||||
|
||||
#[br(big)]
|
||||
add_directories : u32,
|
||||
|
||||
#[br(big)]
|
||||
delete_directories : u32,
|
||||
|
||||
#[br(big)]
|
||||
delete_data_size : u32,
|
||||
|
||||
#[br(big)]
|
||||
delete_data_size_2 : u32,
|
||||
|
||||
#[br(big)]
|
||||
minor_version : u32,
|
||||
|
||||
#[br(big)]
|
||||
repository_name : u32,
|
||||
|
||||
#[br(big)]
|
||||
commands : u32,
|
||||
|
||||
#[br(big)]
|
||||
sqpk_add_commands: u32,
|
||||
|
||||
#[br(big)]
|
||||
sqpk_delete_commands: u32,
|
||||
|
||||
#[br(big)]
|
||||
sqpk_expand_commands: u32,
|
||||
|
||||
#[br(big)]
|
||||
sqpk_header_commands: u32,
|
||||
|
||||
#[br(big)]
|
||||
#[br(pad_after = 0xB8)]
|
||||
sqpk_file_commands: u32
|
||||
}
|
||||
|
||||
#[binrw::binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct ApplyOptionChunk {
|
||||
#[br(pad_after = 4)]
|
||||
option: u32,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[binrw::binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct AddDirectoryChunk {
|
||||
#[br(temp)]
|
||||
path_length: u32,
|
||||
|
||||
#[br(count = path_length)]
|
||||
#[br(map = | x: Vec < u8 > | String::from_utf8(x).unwrap())]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[br(big)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct DeleteDirectoryChunk {
|
||||
#[br(temp)]
|
||||
path_length: u32,
|
||||
|
||||
#[br(count = path_length)]
|
||||
#[br(map = | x: Vec < u8 > | String::from_utf8(x).unwrap())]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum SqpkOperation {
|
||||
#[br(magic = b'A')] AddData(SqpkAddData),
|
||||
#[br(magic = b'D')] DeleteData(SqpkDeleteData),
|
||||
#[br(magic = b'E')] ExpandData(SqpkDeleteData),
|
||||
#[br(magic = b'F')] FileOperation(SqpkFileOperationData),
|
||||
#[br(magic = b'H')] HeaderUpdate(SqpkHeaderUpdateData),
|
||||
#[br(magic = b'I')] IndexAddDelete(SqpkIndexData),
|
||||
#[br(magic = b'X')] PatchInfo(SqpkPatchInfo),
|
||||
#[br(magic = b'T')] TargetInfo(SqpkTargetInfo),
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
enum SqpkIndexCommand {
|
||||
#[br(magic = b'A')] Add,
|
||||
#[br(magic = b'D')] Delete
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkIndexData {
|
||||
command : SqpkIndexCommand,
|
||||
|
||||
#[br(map = | x: u8 | x != 0)]
|
||||
#[br(pad_after = 1)]
|
||||
is_synonym : bool,
|
||||
|
||||
main_id : u16,
|
||||
sub_id : u16,
|
||||
file_id : u32,
|
||||
|
||||
file_hash : u64,
|
||||
|
||||
block_offset : u32,
|
||||
block_number : u32
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
struct SqpkPatchInfo {
|
||||
status: u8,
|
||||
#[br(pad_after = 1)]
|
||||
version: u8,
|
||||
|
||||
#[br(big)]
|
||||
install_size : u64
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum SqpkFileOperation {
|
||||
#[br(magic = b'A')] AddFile,
|
||||
#[br(magic = b'R')] RemoveAll
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkAddData {
|
||||
#[br(pad_before = 3)]
|
||||
main_id : u16,
|
||||
sub_id : u16,
|
||||
file_id : u32,
|
||||
|
||||
#[br(map = | x : i32 | x << 7 )]
|
||||
block_offset : i32,
|
||||
#[br(map = | x : i32 | x << 7 )]
|
||||
block_number : i32,
|
||||
#[br(map = | x : i32 | x << 7 )]
|
||||
block_delete_number : i32,
|
||||
|
||||
#[br(count = block_number)]
|
||||
block_data : Vec<u8>
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkDeleteData {
|
||||
#[br(pad_before = 3)]
|
||||
main_id : u16,
|
||||
sub_id : u16,
|
||||
file_id : u32,
|
||||
|
||||
#[br(map = | x : i32 | x << 7 )]
|
||||
block_offset : i32,
|
||||
#[br(pad_after = 4)]
|
||||
block_number : i32
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum TargetFileKind {
|
||||
#[br(magic=b'D')] Dat,
|
||||
#[br(magic=b'I')] Index
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum TargetHeaderKind {
|
||||
#[br(magic=b'V')] Version,
|
||||
#[br(magic=b'I')] Index,
|
||||
#[br(magic=b'D')] Data
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkHeaderUpdateData {
|
||||
file_kind : TargetFileKind,
|
||||
header_kind : TargetHeaderKind,
|
||||
|
||||
#[br(pad_before = 1)]
|
||||
main_id : u16,
|
||||
sub_id : u16,
|
||||
file_id : u32,
|
||||
|
||||
#[br(count = 1024)]
|
||||
header_data : Vec<u8>
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkFileOperationData {
|
||||
#[br(pad_after = 2)]
|
||||
operation: SqpkFileOperation,
|
||||
|
||||
offset : i64,
|
||||
file_size : u64,
|
||||
#[br(temp)]
|
||||
path_length : u32,
|
||||
expansion_id : u32,
|
||||
|
||||
#[br(count = path_length)]
|
||||
#[br(map = | x: Vec < u8 > | String::from_utf8(x[..x.len() - 1].to_vec()).unwrap())]
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkTargetInfo {
|
||||
#[br(pad_before = 3)]
|
||||
platform : u16,
|
||||
region : i16,
|
||||
is_debug : i16,
|
||||
version : u16,
|
||||
#[br(little)]
|
||||
deleted_data_size : u64,
|
||||
#[br(little)]
|
||||
#[br(pad_after = 96)]
|
||||
seek_count : u64
|
||||
}
|
||||
|
||||
#[derive(BinRead, PartialEq, Debug)]
|
||||
#[br(big)]
|
||||
struct SqpkChunk {
|
||||
size: u32,
|
||||
operation : SqpkOperation
|
||||
}
|
||||
|
||||
pub fn process_patch(data_dir : &str, path : &str) {
|
||||
let mut file = File::open(path).unwrap();
|
||||
|
||||
file.seek(SeekFrom::Start(12));
|
||||
|
||||
loop {
|
||||
let chunk = PatchChunk::read(&mut file).unwrap();
|
||||
|
||||
match chunk.chunk_type {
|
||||
ChunkType::Sqpk(pchunk) => {
|
||||
match pchunk.operation {
|
||||
SqpkOperation::AddData(add) => {
|
||||
let filename = format!("{}/sqpack/ffxiv/{:02x}{:04x}.win32.dat{}", data_dir, add.main_id, add.sub_id, add.file_id);
|
||||
|
||||
let mut new_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(filename).unwrap();
|
||||
|
||||
new_file.seek(SeekFrom::Start(add.block_offset as u64));
|
||||
|
||||
new_file.write(&*add.block_data);
|
||||
|
||||
let wipe_buffer : [u8; 1 << 16] = [0; 1 << 16];
|
||||
|
||||
let mut length = add.block_delete_number;
|
||||
let mut num_bytes = 0;
|
||||
while length > 0 {
|
||||
num_bytes = min(wipe_buffer.len() as i32, add.block_delete_number);
|
||||
new_file.write(&wipe_buffer[0..num_bytes as usize]);
|
||||
length -= num_bytes;
|
||||
}
|
||||
}
|
||||
SqpkOperation::DeleteData(delete) => {
|
||||
let filename = format!("{}/sqpack/ffxiv/{:02x}{:04x}.win32.dat{}", data_dir, delete.main_id, delete.sub_id, delete.file_id);
|
||||
|
||||
let mut new_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(filename).unwrap();
|
||||
|
||||
new_file.seek(SeekFrom::Start(delete.block_offset as u64));
|
||||
|
||||
let wipe_buffer : [u8; 1 << 16] = [0; 1 << 16];
|
||||
|
||||
let mut length = delete.block_number << 7;
|
||||
let mut num_bytes = 0;
|
||||
while length > 0 {
|
||||
num_bytes = min(wipe_buffer.len() as i32, delete.block_number << 7);
|
||||
new_file.write(&wipe_buffer[0..num_bytes as usize]);
|
||||
length -= num_bytes;
|
||||
}
|
||||
|
||||
let block_size : i32 = 1 >> 7;
|
||||
new_file.write(block_size.to_le_bytes().as_slice());
|
||||
|
||||
let unknown : i32 = 0;
|
||||
new_file.write(unknown.to_le_bytes().as_slice());
|
||||
|
||||
let file_size : i32 = 0;
|
||||
new_file.write(file_size.to_le_bytes().as_slice());
|
||||
|
||||
let num_blocks : i32 = delete.block_number - 1;
|
||||
new_file.write(num_blocks.to_le_bytes().as_slice());
|
||||
|
||||
let used_blocks : i32 = 0;
|
||||
new_file.write(used_blocks.to_le_bytes().as_slice());
|
||||
}
|
||||
SqpkOperation::ExpandData(expand) => {
|
||||
let filename = format!("{}/sqpack/ffxiv/{:02x}{:04x}.win32.dat{}", data_dir, expand.main_id, expand.sub_id, expand.file_id);
|
||||
|
||||
let mut new_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(filename).unwrap();
|
||||
|
||||
new_file.seek(SeekFrom::Start(expand.block_offset as u64));
|
||||
|
||||
let wipe_buffer : [u8; 1 << 16] = [0; 1 << 16];
|
||||
|
||||
let mut length = expand.block_number << 7;
|
||||
let mut num_bytes = 0;
|
||||
while length > 0 {
|
||||
num_bytes = min(wipe_buffer.len() as i32, expand.block_number << 7);
|
||||
new_file.write(&wipe_buffer[0..num_bytes as usize]);
|
||||
length -= num_bytes;
|
||||
}
|
||||
|
||||
let block_size : i32 = 1 >> 7;
|
||||
new_file.write(block_size.to_le_bytes().as_slice());
|
||||
|
||||
let unknown : i32 = 0;
|
||||
new_file.write(unknown.to_le_bytes().as_slice());
|
||||
|
||||
let file_size : i32 = 0;
|
||||
new_file.write(file_size.to_le_bytes().as_slice());
|
||||
|
||||
let num_blocks : i32 = expand.block_number - 1;
|
||||
new_file.write(num_blocks.to_le_bytes().as_slice());
|
||||
|
||||
let used_blocks : i32 = 0;
|
||||
new_file.write(used_blocks.to_le_bytes().as_slice());
|
||||
}
|
||||
SqpkOperation::HeaderUpdate(header) => {
|
||||
let mut file_path : String;
|
||||
|
||||
match header.file_kind {
|
||||
TargetFileKind::Dat => {
|
||||
file_path = format!("{}/sqpack/ffxiv/{:02x}{:04x}.win32.dat{}", data_dir, header.main_id, header.sub_id, header.file_id);
|
||||
}
|
||||
TargetFileKind::Index => {
|
||||
file_path = format!("{}/sqpack/ffxiv/{:02x}{:04x}.win32.index", data_dir, header.main_id, header.sub_id);
|
||||
|
||||
// index files have no special ending if it's file_id == 0
|
||||
if header.file_id != 0 {
|
||||
file_path += &*format!("{}", header.file_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(file_path.as_str()).unwrap();
|
||||
|
||||
if header.header_kind != Version {
|
||||
new_file.seek(SeekFrom::Start(1024));
|
||||
}
|
||||
|
||||
new_file.write(&*header.header_data);
|
||||
}
|
||||
SqpkOperation::FileOperation(fop) => {
|
||||
match fop.operation {
|
||||
SqpkFileOperation::AddFile => {
|
||||
let new_path = data_dir.to_owned() + "/" + &fop.path;
|
||||
|
||||
let (left, right) = new_path.rsplit_once('/').unwrap();
|
||||
|
||||
fs::create_dir_all(left);
|
||||
|
||||
// reverse reading crc32
|
||||
file.seek(SeekFrom::Current(-4));
|
||||
|
||||
let mut data: Vec<u8> = Vec::with_capacity(fop.file_size as usize);
|
||||
|
||||
while data.len() < fop.file_size as usize {
|
||||
data.append(&mut read_data_block_patch(&mut file).unwrap());
|
||||
}
|
||||
|
||||
// re-apply crc32
|
||||
file.seek(SeekFrom::Current(4));
|
||||
|
||||
// now apply the file!
|
||||
let mut new_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(new_path).unwrap();
|
||||
new_file.seek(SeekFrom::Start(fop.offset as u64));
|
||||
|
||||
new_file.write(&mut data);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ChunkType::EndOfFile => {
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
99
src/race.rs
Executable file
99
src/race.rs
Executable file
|
@ -0,0 +1,99 @@
|
|||
#[derive(PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Gender {
|
||||
Male,
|
||||
Female,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Subrace {
|
||||
Midlander,
|
||||
Highlander,
|
||||
Wildwood,
|
||||
Duskwight,
|
||||
Plainsfolk,
|
||||
Dunesfolk,
|
||||
Seeker,
|
||||
Keeper,
|
||||
SeaWolf,
|
||||
Hellsguard,
|
||||
Raen,
|
||||
Xaela,
|
||||
Hellion,
|
||||
Lost,
|
||||
Rava,
|
||||
Veena,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Race {
|
||||
Hyur,
|
||||
Elezen,
|
||||
Lalafell,
|
||||
Miqote,
|
||||
Roegadyn,
|
||||
AuRa,
|
||||
Hrothgar,
|
||||
Viera,
|
||||
}
|
||||
|
||||
mod internal_race {
|
||||
use crate::race::Race;
|
||||
use crate::race::Subrace;
|
||||
use crate::race::Gender;
|
||||
use crate::race::Race::*;
|
||||
use crate::race::Subrace::*;
|
||||
use crate::race::Gender::*;
|
||||
use paste::paste;
|
||||
use crate::define_race_enum;
|
||||
|
||||
define_race_enum! {
|
||||
pub enum RaceTest {
|
||||
[101](Hyur, Male, Midlander),
|
||||
[201](Hyur, Female, Midlander),
|
||||
[301](Hyur, Male, Highlander),
|
||||
[401](Hyur, Female, Highlander),
|
||||
|
||||
[501](Elezen, Male),
|
||||
[601](Elezen, Female),
|
||||
|
||||
[701](Miqote, Male),
|
||||
[801](Miqote, Female),
|
||||
|
||||
[901](Roegadyn, Male),
|
||||
[1001](Roegadyn, Female),
|
||||
|
||||
[1101](Lalafell, Male),
|
||||
[1201](Lalafell, Female),
|
||||
|
||||
[1301](AuRa, Male),
|
||||
[1401](AuRa, Female),
|
||||
|
||||
[1501](Hrothgar, Male),
|
||||
[1601](Hrothgar, Female),
|
||||
|
||||
[1701](Viera, Male),
|
||||
[1801](Viera, Female)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a proper race identifier (such as 101, for Hyur-Midlander-Males) given a race, a subrace,
|
||||
/// and a gender.
|
||||
pub fn get_race_id(race: Race, subrace: Subrace, gender: Gender) -> Option<i32> {
|
||||
Some(internal_race::convert_to_internal(race, subrace, gender).unwrap() as i32)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::race::internal_race::{convert_to_internal, RaceTest};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_convert_to_internal() {
|
||||
assert_eq!(convert_to_internal(Race::Hyur, Subrace::Midlander, Gender::Female).unwrap(),
|
||||
RaceTest::HyurMidlanderFemale);
|
||||
}
|
||||
}
|
194
src/repository.rs
Executable file
194
src/repository.rs
Executable file
|
@ -0,0 +1,194 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::cmp::Ordering::{Greater, Less};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use crate::repository::RepositoryType::{Base, Expansion};
|
||||
|
||||
/// The type of repository, discerning game data from expansion data.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RepositoryType {
|
||||
/// The base game directory, like "ffxiv".
|
||||
Base,
|
||||
/// An expansion directory, like "ex1".
|
||||
Expansion {
|
||||
/// The expansion number starting at 1.
|
||||
number: i32
|
||||
},
|
||||
}
|
||||
|
||||
/// Encapsulates a directory of game data, such as "ex1". This data is also versioned.
|
||||
/// This handles calculating the correct dat and index filenames, mainly for `GameData`.
|
||||
#[derive(Debug)]
|
||||
pub struct Repository {
|
||||
/// The folder name, such as "ex1".
|
||||
pub name: String,
|
||||
/// The type of repository, such as "base game" or "expansion".
|
||||
pub repo_type: RepositoryType,
|
||||
/// The version of the game data.
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Eq for Repository {}
|
||||
|
||||
impl PartialEq for Repository {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Repository {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// This ensures that the ordering of the repositories is always ffxiv, ex1, ex2 and so on.
|
||||
match self.repo_type {
|
||||
Base => Less,
|
||||
Expansion { number } => {
|
||||
let super_number = number;
|
||||
match other.repo_type {
|
||||
Base => Greater,
|
||||
Expansion { number } => super_number.cmp(&number)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Repository {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_version(p: &Path) -> Option<String> {
|
||||
fs::read_to_string(p).ok()
|
||||
}
|
||||
|
||||
/// This refers to the specific root directory a file is located in.
|
||||
/// This is a fixed list of directories, and all of them are known.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Category {
|
||||
/// Common files such as game fonts, and other data that doesn't really fit anywhere else.
|
||||
Common,
|
||||
/// Shared data between game maps.
|
||||
BgCommon,
|
||||
/// Game map data such as models, textures, and so on.
|
||||
Bg,
|
||||
/// Cutscene content such as animations.
|
||||
Cut,
|
||||
/// Character model files and more.
|
||||
Chara,
|
||||
/// Compiled shaders used by the retail client.
|
||||
Shader,
|
||||
/// UI layouts and textures.
|
||||
UI,
|
||||
/// Sound effects, basically anything not under `Music`.
|
||||
Sound,
|
||||
/// This "VFX" means "visual effects", and contains textures and definitions for stuff like battle effects.
|
||||
VFX,
|
||||
/// A leftover from 1.0, where the UI was primarily driven by LUA scripts.
|
||||
UIScript,
|
||||
/// Excel data.
|
||||
EXD,
|
||||
/// Many game events are driven by LUA scripts, such as cutscenes.
|
||||
GameScript,
|
||||
/// Music!
|
||||
Music,
|
||||
/// Unknown purpose, most likely to test SqPack functionality.
|
||||
SqPackTest,
|
||||
/// Unknown purpose, msot likely debug files.
|
||||
Debug,
|
||||
}
|
||||
|
||||
pub fn string_to_category(string: &str) -> Option<Category> {
|
||||
use crate::repository::Category::*;
|
||||
|
||||
match string {
|
||||
"common" => Some(Common),
|
||||
"bgcommon" => Some(BgCommon),
|
||||
"bg" => Some(Bg),
|
||||
"cut" => Some(Cut),
|
||||
"chara" => Some(Chara),
|
||||
"shader" => Some(Shader),
|
||||
"ui" => Some(UI),
|
||||
"sound" => Some(Sound),
|
||||
"vfx" => Some(VFX),
|
||||
"ui_script" => Some(UIScript),
|
||||
"exd" => Some(EXD),
|
||||
"game_script" => Some(GameScript),
|
||||
"music" => Some(Music),
|
||||
"sqpack_test" => Some(SqPackTest),
|
||||
"debug" => Some(Debug),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
impl Repository {
|
||||
/// Creates a new `Repository`, from an existing repository directory. This may return `None` if
|
||||
/// the directory is invalid, e.g. a version file is missing.
|
||||
pub fn from_existing(dir: &str) -> Option<Repository> {
|
||||
let path = Path::new(dir);
|
||||
if path.metadata().is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = String::from(path.file_stem().unwrap().to_str().unwrap());
|
||||
|
||||
let repo_type: RepositoryType;
|
||||
|
||||
if name == "ffxiv" {
|
||||
repo_type = Base;
|
||||
} else {
|
||||
repo_type = Expansion {
|
||||
number: 0
|
||||
}
|
||||
}
|
||||
|
||||
let version: Option<String>;
|
||||
|
||||
if repo_type == Base {
|
||||
let mut d = PathBuf::from(dir);
|
||||
d.pop();
|
||||
d.pop();
|
||||
d.push("ffxivgame.ver");
|
||||
|
||||
version = read_version(d.as_path());
|
||||
} else {
|
||||
let mut d = PathBuf::from(dir);
|
||||
d.push(format!("{}.ver", name));
|
||||
|
||||
version = read_version(d.as_path());
|
||||
}
|
||||
|
||||
if version == None {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Repository {
|
||||
name,
|
||||
repo_type,
|
||||
version: version.unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn expansion(&self) -> i32 {
|
||||
match self.repo_type {
|
||||
Base => 0,
|
||||
Expansion { number } => number
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate an index filename for a specific category, like _"0a0000.win32.index"_.
|
||||
pub fn index_filename(&self, category: Category) -> String {
|
||||
format!("{:02x}{:02}{:02}.{}.index",
|
||||
category as i32, self.expansion(), 0, "win32")
|
||||
}
|
||||
|
||||
/// Calculate a dat filename given a category and a data file id, returns something like _"0a0000.win32.dat0"_.
|
||||
pub fn dat_filename(&self, category: Category, data_file_id: u32) -> String {
|
||||
let expansion = self.expansion();
|
||||
let chunk = 0;
|
||||
let platform = "win32";
|
||||
|
||||
format!("{:02x}{expansion:02}{chunk:02}.{platform}.dat{data_file_id}",
|
||||
category as u32)
|
||||
}
|
||||
}
|
78
src/sqpack.rs
Executable file
78
src/sqpack.rs
Executable file
|
@ -0,0 +1,78 @@
|
|||
use std::io::{Read, Seek, SeekFrom};
|
||||
use crc::{Crc, CRC_32_JAMCRC};
|
||||
use crate::compression::no_header_decompress;
|
||||
use crate::dat::{BlockHeader, CompressionMode};
|
||||
use binrw::BinRead;
|
||||
|
||||
const JAMCRC: Crc<u32> = Crc::<u32>::new(&CRC_32_JAMCRC);
|
||||
|
||||
/// Calculates a hash for `index` files from a game path.
|
||||
pub fn calculate_hash(path: &str) -> u64 {
|
||||
let lowercase = path.to_lowercase();
|
||||
|
||||
let pos = lowercase.rfind('/').unwrap();
|
||||
|
||||
let (directory, filename) = lowercase.split_at(pos);
|
||||
|
||||
let directory_crc = JAMCRC.checksum(directory.as_bytes());
|
||||
let filename_crc = JAMCRC.checksum(filename[1..filename.len()].as_bytes());
|
||||
|
||||
(directory_crc as u64) << 32 | (filename_crc as u64)
|
||||
}
|
||||
|
||||
pub fn read_data_block<T : Read + Seek>(mut buf : T, starting_position: u64) -> Option<Vec<u8>> {
|
||||
buf.seek(SeekFrom::Start(starting_position)).ok()?;
|
||||
|
||||
let block_header = BlockHeader::read(&mut buf).unwrap();
|
||||
|
||||
match block_header.compression {
|
||||
CompressionMode::Compressed { compressed_length, decompressed_length } => {
|
||||
let mut compressed_data: Vec<u8> = vec![0; compressed_length as usize];
|
||||
buf.read_exact(&mut compressed_data).ok()?;
|
||||
|
||||
let mut decompressed_data: Vec<u8> = vec![0; decompressed_length as usize];
|
||||
if !no_header_decompress(&mut compressed_data, &mut decompressed_data) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(decompressed_data)
|
||||
}
|
||||
CompressionMode::Uncompressed { file_size } => {
|
||||
let mut local_data: Vec<u8> = vec![0; file_size as usize];
|
||||
buf.read_exact(&mut local_data).ok()?;
|
||||
|
||||
Some(local_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A fixed version of read_data_block accounting for differing compressed block sizes in ZiPatch files.
|
||||
pub fn read_data_block_patch<T : Read + Seek>(mut buf : T) -> Option<Vec<u8>> {
|
||||
let block_header = BlockHeader::read(&mut buf).unwrap();
|
||||
|
||||
match block_header.compression {
|
||||
CompressionMode::Compressed { compressed_length, decompressed_length } => {
|
||||
let compressed_length : usize = ((compressed_length as usize + 143) & 0xFFFFFF80) - (block_header.size as usize);
|
||||
|
||||
let mut compressed_data: Vec<u8> = vec![0; compressed_length as usize];
|
||||
buf.read_exact(&mut compressed_data).ok()?;
|
||||
|
||||
let mut decompressed_data: Vec<u8> = vec![0; decompressed_length as usize];
|
||||
if !no_header_decompress(&mut compressed_data, &mut decompressed_data) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(decompressed_data)
|
||||
}
|
||||
CompressionMode::Uncompressed { file_size } => {
|
||||
let new_file_size : usize = ((file_size as usize + 143) & 0xFFFFFF80);
|
||||
|
||||
let mut local_data: Vec<u8> = vec![0; file_size as usize];
|
||||
buf.read_exact(&mut local_data).ok()?;
|
||||
|
||||
buf.seek(SeekFrom::Current((new_file_size as usize - block_header.size as usize - file_size as usize) as i64)).ok()?;
|
||||
|
||||
Some(local_data)
|
||||
}
|
||||
}
|
||||
}
|
19
tests/integration_test.rs
Executable file
19
tests/integration_test.rs
Executable file
|
@ -0,0 +1,19 @@
|
|||
use std::env;
|
||||
|
||||
#[test]
|
||||
fn test_index_read() {
|
||||
let game_dir = env::var("FFXIV_GAME_DIR").unwrap();
|
||||
|
||||
physis::index::IndexFile::from_existing(format!("{}/game/sqpack/ffxiv/000000.win32.index", game_dir).as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gamedata_extract() {
|
||||
let game_dir = env::var("FFXIV_GAME_DIR").unwrap();
|
||||
|
||||
let mut gamedata = physis::gamedata::GameData::from_existing(format!("{}/game", game_dir).as_str()).unwrap();
|
||||
|
||||
gamedata.reload_repositories();
|
||||
|
||||
assert!(gamedata.extract("exd/root.exl").is_some());
|
||||
}
|
Loading…
Add table
Reference in a new issue