mirror of
https://github.com/Theaninova/mhlib.git
synced 2025-12-12 20:46:20 +00:00
initial commit
This commit is contained in:
3
rust/.gitignore
vendored
Normal file
3
rust/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
.idea
|
||||
mhjnr.iml
|
||||
832
rust/Cargo.lock
generated
Normal file
832
rust/Cargo.lock
generated
Normal file
@@ -0,0 +1,832 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "array-init"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "binrw"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384"
|
||||
dependencies = [
|
||||
"array-init",
|
||||
"binrw_derive",
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binrw_derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8"
|
||||
dependencies = [
|
||||
"either",
|
||||
"owo-colors",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"flume",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide 0.6.2",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.10.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"pin-project",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
|
||||
|
||||
[[package]]
|
||||
name = "godot"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"godot-core",
|
||||
"godot-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"godot4-prebuilt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-codegen"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"godot-bindings",
|
||||
"heck",
|
||||
"nanoserde",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"godot-codegen",
|
||||
"godot-ffi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-ffi"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"godot-bindings",
|
||||
"godot-codegen",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"venial",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot4-prebuilt"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=4.0.1#f9e8cfec0ec565201801b02160ef836800746617"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
"qoi",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[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.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mhjnr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"binrw",
|
||||
"godot",
|
||||
"image",
|
||||
"itertools",
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755e7965536bc54d7c9fba2df5ada5bf835b0443fd613f0a53fa199a301839d3"
|
||||
dependencies = [
|
||||
"nanoserde-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde-derive"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7a94da6c6181c35d043fc61c43ac96d3a5d739e7b8027f77650ba41504d6ab"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[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.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-xml-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"jpeg-decoder",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "venial"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
22
rust/Cargo.toml
Normal file
22
rust/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "mhjnr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "mhjnr-viewer"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "mhjnr"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.10.5"
|
||||
unicode-segmentation = "1.10.1"
|
||||
binrw = "0.11.1"
|
||||
serde = {version = "1.0.160", features = ["derive"]}
|
||||
serde-xml-rs = "0.6.0"
|
||||
image = "0.24.6"
|
||||
base64 = "0.21.0"
|
||||
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }
|
||||
46
rust/src/formats/datafile.rs
Normal file
46
rust/src/formats/datafile.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use binrw::{binread, NullString};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[binread]
|
||||
#[br(little, magic = b"MHJNR")]
|
||||
#[derive(Debug)]
|
||||
pub struct Datafile {
|
||||
#[br(align_after = 0x20)]
|
||||
pub edition: Edition,
|
||||
|
||||
#[br(temp)]
|
||||
pub count: u32,
|
||||
#[br(align_after = 0x20)]
|
||||
pub unk1: u32,
|
||||
|
||||
#[br(count = count)]
|
||||
pub files: Vec<FileEntry>,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
pub enum Edition {
|
||||
#[br(magic = b"-XS")]
|
||||
Xs,
|
||||
#[br(magic = b"-XXL")]
|
||||
Xxl,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[binread]
|
||||
pub struct FileEntry {
|
||||
#[br(pad_size_to = 0x68)]
|
||||
pub name: NullString,
|
||||
pub pos: u32,
|
||||
#[br(pad_after = 0x10)]
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
impl Datafile {
|
||||
pub fn into_index(self) -> HashMap<String, FileEntry> {
|
||||
self.files
|
||||
.into_iter()
|
||||
.map(|entry| (entry.name.to_string(), entry))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
46
rust/src/formats/level.rs
Normal file
46
rust/src/formats/level.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use binrw::prelude::*;
|
||||
use binrw::{BinRead, Error};
|
||||
use image;
|
||||
use image::error::{DecodingError, ImageFormatHint};
|
||||
use image::{ImageError, ImageResult, Rgb, RgbImage};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
pub struct LevelTile {
|
||||
pub index: u8,
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
pub struct LevelLayer {
|
||||
pub tile_count: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub unknown_2: u32,
|
||||
#[br(count = width * height)]
|
||||
pub tiles: Vec<LevelTile>,
|
||||
}
|
||||
|
||||
pub fn level_tile_data_to_image(tile_data: &[u8]) -> ImageResult<RgbImage> {
|
||||
let mut cursor = Cursor::new(tile_data);
|
||||
let layer = LevelLayer::read(&mut cursor).map_err(to_decoding_err)?;
|
||||
|
||||
let mut image = RgbImage::new(layer.width, layer.height);
|
||||
for y in 0..layer.height {
|
||||
for x in 0..layer.width {
|
||||
let tile = LevelTile::read(&mut cursor).map_err(to_decoding_err)?;
|
||||
image.put_pixel(x, y, Rgb([tile.id, tile.index, 0]));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn to_decoding_err(err: Error) -> ImageError {
|
||||
ImageError::Decoding(DecodingError::new(
|
||||
ImageFormatHint::Name(String::from("mhjnr_layer")),
|
||||
err,
|
||||
))
|
||||
}
|
||||
115
rust/src/formats/mod.rs
Normal file
115
rust/src/formats/mod.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::formats::datafile::FileEntry;
|
||||
use crate::formats::level::LevelLayer;
|
||||
use crate::formats::rle::RleImage;
|
||||
use crate::formats::sprites::Sprites;
|
||||
use crate::formats::txt::{decrypt_txt, DecryptError};
|
||||
use crate::formats::ui_xml::UiTag;
|
||||
use binrw::BinRead;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
pub mod datafile;
|
||||
pub mod level;
|
||||
pub mod rle;
|
||||
pub mod sprites;
|
||||
pub mod txt;
|
||||
pub mod ui_xml;
|
||||
|
||||
pub enum DatafileFile {
|
||||
Txt(String),
|
||||
Level(LevelLayer),
|
||||
Sprites(Vec<Sprites>),
|
||||
RleSprite(Box<RleImage>),
|
||||
Bitmap(Vec<u8>),
|
||||
Vorbis(Vec<u8>),
|
||||
TileCollision(String),
|
||||
Ui(UiTag),
|
||||
}
|
||||
|
||||
pub enum Error {
|
||||
Deserialization,
|
||||
UnknownFormat(String),
|
||||
UnknownError,
|
||||
Custom(String),
|
||||
DecryptError(DecryptError),
|
||||
}
|
||||
|
||||
fn custom_err<T>(e: T) -> Error
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
Error::Custom(format!("{:#?}", e))
|
||||
}
|
||||
|
||||
pub fn load_data<R>(entry: &FileEntry, reader: &mut R) -> Result<DatafileFile, Error>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
reader
|
||||
.seek(SeekFrom::Start(entry.pos as u64))
|
||||
.map_err(custom_err)?;
|
||||
let mut data = vec![0u8; entry.len as usize];
|
||||
reader.read_exact(&mut data).map_err(custom_err)?;
|
||||
|
||||
let name = entry.name.to_string();
|
||||
let path = Path::new(&name);
|
||||
|
||||
match path
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or(Error::Custom("No extension".to_string()))?
|
||||
{
|
||||
"dat" => Ok(DatafileFile::Level(
|
||||
LevelLayer::read(&mut Cursor::new(data)).map_err(custom_err)?,
|
||||
)),
|
||||
"rle" => Ok(DatafileFile::RleSprite(Box::new(
|
||||
RleImage::read(&mut Cursor::new(data)).map_err(custom_err)?,
|
||||
))),
|
||||
"bmp" => Ok(DatafileFile::Bitmap(data)),
|
||||
"ogg" => Ok(DatafileFile::Vorbis(data)),
|
||||
"xml" => {
|
||||
serde_xml_rs::from_str::<UiTag>(String::from_utf8(data).map_err(custom_err)?.as_str())
|
||||
.map_err(custom_err)
|
||||
.map(DatafileFile::Ui)
|
||||
}
|
||||
"txt" => {
|
||||
let stem = path
|
||||
.file_stem()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or(Error::Custom("Stem".to_string()))?;
|
||||
let decr = decrypt_txt(data.into_iter()).map_err(|e| Error::DecryptError(e))?;
|
||||
if stem.starts_with("tile_collision") {
|
||||
Ok(DatafileFile::TileCollision(decr))
|
||||
} else if stem == "sprites" {
|
||||
Ok(DatafileFile::Sprites(
|
||||
Sprites::parse(decr.as_str()).map_err(custom_err)?,
|
||||
))
|
||||
} else {
|
||||
Ok(DatafileFile::Txt(decr))
|
||||
}
|
||||
}
|
||||
/*Some("rle") => {
|
||||
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.gif");
|
||||
println!("{:?}", path);
|
||||
let mut encoder = GifEncoder::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.unwrap(),
|
||||
);
|
||||
encoder.set_repeat(Repeat::Infinite).unwrap();
|
||||
encoder.try_encode_frames(image.into_frames()).unwrap();
|
||||
}
|
||||
Some("dat") => {
|
||||
let image = level_tile_data_to_image(&data).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.png");
|
||||
println!("{:?}", path);
|
||||
image.save_with_format(path, ImageFormat::Png).unwrap();
|
||||
}*/
|
||||
ext => Err(Error::UnknownFormat(ext.to_string())),
|
||||
}
|
||||
}
|
||||
131
rust/src/formats/rle.rs
Normal file
131
rust/src/formats/rle.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use binrw::prelude::*;
|
||||
use binrw::Endian;
|
||||
use image::error::{DecodingError, ImageFormatHint};
|
||||
use image::{AnimationDecoder, Delay, Frame, Frames, ImageBuffer, ImageError};
|
||||
use std::io::{Read, Seek};
|
||||
use std::time::Duration;
|
||||
|
||||
#[binread]
|
||||
#[br(little, magic = 0x67u32)]
|
||||
pub struct RleImage {
|
||||
pub hash: u64,
|
||||
pub color_table: [[u8; 4]; 512],
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub numerator: u32,
|
||||
pub denominator: u32,
|
||||
#[br(temp)]
|
||||
pub frame_count: u32,
|
||||
#[br(count = frame_count)]
|
||||
pub frames: Vec<RleLayer>,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[br(little)]
|
||||
pub struct RleLayer {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub left: u32,
|
||||
pub top: u32,
|
||||
pub numerator: u32,
|
||||
pub denominator: u32,
|
||||
pub data_size: u32,
|
||||
pub unknown3: u32,
|
||||
#[br(args(width * height), parse_with = parse_rle)]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_rle<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
endian: Endian,
|
||||
(size,): (u32,),
|
||||
) -> BinResult<Vec<u8>> {
|
||||
let mut data = Vec::with_capacity(size as usize);
|
||||
|
||||
while data.len() != size as usize {
|
||||
let count: i8 = reader.read_type(endian)?;
|
||||
if count > 0 {
|
||||
let value: u8 = reader.read_type(endian)?;
|
||||
for _ in 0..count {
|
||||
data.push(value);
|
||||
}
|
||||
} else {
|
||||
for _ in 0..-count {
|
||||
data.push(reader.read_type(endian)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
impl RleImage {
|
||||
pub fn get_image_data(&self, layer: &RleLayer) -> Vec<u8> {
|
||||
let mut data = Vec::<u8>::with_capacity(self.width as usize * self.height as usize * 4);
|
||||
let mut i = 0;
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
if y < layer.top
|
||||
|| y >= layer.top + layer.height
|
||||
|| x < layer.left
|
||||
|| x >= layer.left + layer.width
|
||||
{
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
} else {
|
||||
let color = self.color_table[layer.data[i] as usize];
|
||||
i += 1;
|
||||
data.push(color[2]);
|
||||
data.push(color[1]);
|
||||
data.push(color[0]);
|
||||
data.push(if color[2] == 0 && color[1] == 0 && color[0] == 0 {
|
||||
0
|
||||
} else {
|
||||
255
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnimationDecoder<'a> for RleImage {
|
||||
fn into_frames(self) -> Frames<'a> {
|
||||
Frames::new(Box::new(self.frames.into_iter().map(move |frame| {
|
||||
let buffer = ImageBuffer::from_raw(
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame
|
||||
.data
|
||||
.into_iter()
|
||||
.flat_map(|it| bgra_to_rgba(self.color_table[it as usize]))
|
||||
.collect(),
|
||||
)
|
||||
.ok_or(to_rle_image_err(std::fmt::Error::default()))?;
|
||||
Ok(Frame::from_parts(
|
||||
buffer,
|
||||
frame.left,
|
||||
frame.top,
|
||||
Delay::from_saturating_duration(Duration::from_millis(80)),
|
||||
))
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bgra_to_rgba(pixel: [u8; 4]) -> [u8; 4] {
|
||||
[pixel[2], pixel[1], pixel[0], pixel[3]]
|
||||
}
|
||||
|
||||
fn to_rle_image_err<T>(err: T) -> ImageError
|
||||
where
|
||||
T: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
ImageError::Decoding(DecodingError::new(
|
||||
ImageFormatHint::Name(String::from("mhjnr_rle")),
|
||||
err,
|
||||
))
|
||||
}
|
||||
75
rust/src/formats/sprites.rs
Normal file
75
rust/src/formats/sprites.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#[derive(Debug)]
|
||||
pub struct Sprites {
|
||||
pub name: String,
|
||||
pub sprite_type: SpriteType,
|
||||
pub file_name: String,
|
||||
pub render_mode: RenderMode,
|
||||
pub frames: Option<CropMode>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidData,
|
||||
UnknownEnum(String),
|
||||
}
|
||||
|
||||
impl Sprites {
|
||||
pub fn parse(string: &str) -> Result<Vec<Self>, Error> {
|
||||
string
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(Sprites::parse_single)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn parse_single(string: &str) -> Result<Self, Error> {
|
||||
let mut components = string.split_whitespace();
|
||||
|
||||
Ok(Sprites {
|
||||
file_name: components.next().ok_or(Error::InvalidData)?.to_string(),
|
||||
sprite_type: match components.next().ok_or(Error::InvalidData)? {
|
||||
"anim_rle" => SpriteType::AnimRle,
|
||||
"anim" => SpriteType::Anim,
|
||||
"static" => SpriteType::Static,
|
||||
e => return Err(Error::UnknownEnum(e.to_string())),
|
||||
},
|
||||
name: components.next().ok_or(Error::InvalidData)?.to_string(),
|
||||
render_mode: match components.next().ok_or(Error::InvalidData)? {
|
||||
"normx" => RenderMode::NormX,
|
||||
"flipx" => RenderMode::FlipX,
|
||||
e => return Err(Error::UnknownEnum(e.to_string())),
|
||||
},
|
||||
frames: if let Some(c) = components.next() {
|
||||
Some(match c {
|
||||
"nocrop" => CropMode::NoCrop,
|
||||
x => x
|
||||
.parse::<i32>()
|
||||
.map(CropMode::FrameCount)
|
||||
.map_err(|e| Error::UnknownEnum(e.to_string()))?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CropMode {
|
||||
FrameCount(i32),
|
||||
NoCrop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderMode {
|
||||
NormX,
|
||||
FlipX,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SpriteType {
|
||||
Static,
|
||||
Anim,
|
||||
AnimRle,
|
||||
}
|
||||
68
rust/src/formats/txt.rs
Normal file
68
rust/src/formats/txt.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::num::ParseIntError;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DecryptError {
|
||||
FromUtf8Error(FromUtf8Error),
|
||||
ParseIntError(ParseIntError),
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for DecryptError {
|
||||
fn from(e: FromUtf8Error) -> DecryptError {
|
||||
DecryptError::FromUtf8Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for DecryptError {
|
||||
fn from(e: ParseIntError) -> DecryptError {
|
||||
DecryptError::ParseIntError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypts txt files contained inside the dat file
|
||||
pub fn decrypt_txt<I>(buffer: I) -> Result<String, DecryptError>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let mut key = 0x1234u16;
|
||||
|
||||
String::from_utf8(
|
||||
buffer
|
||||
.map(|char| {
|
||||
let decr = char ^ key as u8;
|
||||
key = key.wrapping_mul(3).wrapping_add(2);
|
||||
decr
|
||||
})
|
||||
.map(|char| (((char >> 1) ^ (char << 1)) & 0x55) ^ (char << 1))
|
||||
.collect(),
|
||||
)
|
||||
.map_err(DecryptError::from)
|
||||
}
|
||||
|
||||
/// Parses a hex string to a Vec<u8>
|
||||
fn from_hex(line: &str) -> Result<Vec<u8>, ParseIntError> {
|
||||
(0..line.len())
|
||||
.step_by(2)
|
||||
.map(|i| u8::from_str_radix(line.get(i..=i + 1).unwrap_or(""), 16))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// This function is applied to *exposed* txt files,
|
||||
/// such as the player profile or high scores
|
||||
///
|
||||
/// If the file is contained in the datafile, it has
|
||||
/// to first be decrypted normally and then again
|
||||
/// with this function.
|
||||
pub fn decrypt_exposed_txt(contents: String) -> Result<String, DecryptError> {
|
||||
contents
|
||||
.split_terminator("\r\n")
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(from_hex)
|
||||
.map(|line| decrypt_txt(line.map_err(DecryptError::from)?.into_iter()))
|
||||
.collect::<Result<Vec<String>, _>>()
|
||||
.map(|l| l.join("\r\n"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
||||
77
rust/src/formats/ui_xml.rs
Normal file
77
rust/src/formats/ui_xml.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum UiTag {
|
||||
Menu(UiMenu),
|
||||
Image(UiImage),
|
||||
TextButton(UiTextButton),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UiMenu {
|
||||
pub selected: String,
|
||||
#[serde(rename = "OnBack")]
|
||||
pub on_back: String,
|
||||
#[serde(rename = "$value")]
|
||||
pub children: Vec<UiTag>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UiImage {
|
||||
pub texture: String,
|
||||
#[serde(deserialize_with = "deserialize_vec2")]
|
||||
pub position: [i32; 2],
|
||||
#[serde(deserialize_with = "deserialize_vec2")]
|
||||
pub size: [i32; 2],
|
||||
#[serde(rename = "fademode")]
|
||||
pub fade_mode: FadeMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UiTextButton {
|
||||
pub name: Option<String>,
|
||||
pub text: String,
|
||||
#[serde(deserialize_with = "deserialize_vec2")]
|
||||
pub position: [i32; 2],
|
||||
#[serde(rename = "halign")]
|
||||
pub horizontal_align: HorizontalAlign,
|
||||
#[serde(rename = "fademode")]
|
||||
pub fade_mode: FadeMode,
|
||||
#[serde(rename = "OnSelect")]
|
||||
pub on_select: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum HorizontalAlign {
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FadeMode {
|
||||
None,
|
||||
}
|
||||
|
||||
fn deserialize_vec2<'de, D>(deserializer: D) -> Result<[i32; 2], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let buf = String::deserialize(deserializer)?;
|
||||
let mut values: Vec<Result<i32, D::Error>> = buf
|
||||
.split(',')
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
// there's some typos so we have to cover that...
|
||||
value.split_ascii_whitespace().collect::<Vec<&str>>()[0]
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.map_err(|err| Error::custom(err.to_string()))
|
||||
})
|
||||
.collect();
|
||||
let y = values.pop().ok_or(Error::custom("InvalidField"))??;
|
||||
let x = values.pop().ok_or(Error::custom("InvalidField"))??;
|
||||
|
||||
Ok([x, y])
|
||||
}
|
||||
272
rust/src/godot/datafile.rs
Normal file
272
rust/src/godot/datafile.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use crate::formats;
|
||||
use crate::formats::datafile::{Datafile, FileEntry};
|
||||
use crate::formats::sprites::{CropMode, RenderMode, SpriteType};
|
||||
use crate::formats::{load_data, DatafileFile};
|
||||
use crate::godot::font::load_bitmap_font;
|
||||
use crate::godot::game_object::parse_game_object;
|
||||
use crate::godot::image::{load_bmp_as_image_texture, load_rle_as_sprite_frames};
|
||||
use crate::godot::sprites::load_sprite_frames;
|
||||
use crate::godot::tile_map::{create_tile_map, TileCollision};
|
||||
use crate::godot::ui::convert_ui;
|
||||
use binrw::BinRead;
|
||||
use godot::engine::global::Error;
|
||||
use godot::engine::image::Format;
|
||||
use godot::engine::resource_loader::CacheMode;
|
||||
use godot::engine::resource_saver::SaverFlags;
|
||||
use godot::engine::utilities::{printerr, prints};
|
||||
use godot::engine::{
|
||||
AtlasTexture, AudioStream, AudioStreamOggVorbis, DirAccess, OggPacketSequence,
|
||||
PlaceholderTexture2D, SpriteFrames,
|
||||
};
|
||||
use godot::engine::{Image, PckPacker};
|
||||
use godot::engine::{ImageTexture, ProjectSettings};
|
||||
use godot::engine::{ResourceFormatLoader, ResourceSaver};
|
||||
use godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
|
||||
use godot::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::str::FromStr;
|
||||
|
||||
const DAT_PATH: &str = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=ResourceFormatLoader)]
|
||||
pub struct DatafileLoader {
|
||||
pub datafile_table: HashMap<String, FileEntry>,
|
||||
|
||||
#[base]
|
||||
pub base: Base<ResourceFormatLoader>,
|
||||
}
|
||||
|
||||
fn convert_path(path: &GodotString) -> String {
|
||||
path.to_string()
|
||||
.strip_prefix("datafile://")
|
||||
.map(|it| it.replace('/', "\\"))
|
||||
.expect("Invalid path")
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl DatafileLoader {
|
||||
fn save_to_cache(&self, resource: Gd<Resource>, path: String) {
|
||||
let cache_path = self.get_cache_path(path);
|
||||
match DirAccess::make_dir_recursive_absolute(cache_path.rsplit_once('/').unwrap().0.into())
|
||||
{
|
||||
Error::OK => (),
|
||||
error => printerr(error.to_variant(), &[]),
|
||||
}
|
||||
ResourceSaver::singleton().save(resource, cache_path.into(), SaverFlags::FLAG_NONE);
|
||||
}
|
||||
|
||||
fn get_cache_path(&self, path: String) -> String {
|
||||
format!(
|
||||
"{}/.cache/{}",
|
||||
DAT_PATH
|
||||
.replace('\\', "/")
|
||||
.strip_suffix("datafile.dat")
|
||||
.unwrap(),
|
||||
path.replace('\\', "/")
|
||||
)
|
||||
}
|
||||
|
||||
fn retrieve_cache<T>(&self, path: String) -> Option<Gd<T>>
|
||||
where
|
||||
T: GodotClass + Inherits<Resource>,
|
||||
{
|
||||
let cache_path = self.get_cache_path(path);
|
||||
let type_hint = T::CLASS_NAME;
|
||||
if !ResourceLoader::singleton().exists(cache_path.clone().into(), type_hint.into()) {
|
||||
return None;
|
||||
}
|
||||
ResourceLoader::singleton()
|
||||
.load(
|
||||
cache_path.into(),
|
||||
type_hint.into(),
|
||||
CacheMode::CACHE_MODE_REUSE,
|
||||
)
|
||||
.map(|it| it.cast())
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ResourceFormatLoaderVirtual for DatafileLoader {
|
||||
fn init(base: Base<Self::Base>) -> Self {
|
||||
let mut file = File::open(DAT_PATH).unwrap();
|
||||
let datafile_table = Datafile::read(&mut file).unwrap().into_index();
|
||||
|
||||
DatafileLoader {
|
||||
base,
|
||||
datafile_table,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_recognized_extensions(&self) -> PackedStringArray {
|
||||
PackedStringArray::from(&[
|
||||
"xml".into(),
|
||||
"txt".into(),
|
||||
"rle".into(),
|
||||
"bmp".into(),
|
||||
"dat".into(),
|
||||
])
|
||||
}
|
||||
|
||||
fn recognize_path(&self, path: GodotString, _type: StringName) -> bool {
|
||||
path.to_string().starts_with("datafile://")
|
||||
}
|
||||
|
||||
fn get_resource_type(&self, path: GodotString) -> GodotString {
|
||||
if path.to_string().ends_with(".dat") {
|
||||
"PackedScene".into()
|
||||
} else {
|
||||
"Resource".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_resource_script_class(&self, _path: GodotString) -> GodotString {
|
||||
GodotString::from_str("").unwrap()
|
||||
}
|
||||
|
||||
fn exists(&self, path: GodotString) -> bool {
|
||||
self.datafile_table
|
||||
.contains_key(convert_path(&path).as_str())
|
||||
}
|
||||
|
||||
fn get_classes_used(&self, _path: GodotString) -> PackedStringArray {
|
||||
PackedStringArray::from(&[])
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
path: GodotString,
|
||||
_original_path: GodotString,
|
||||
_use_sub_threads: bool,
|
||||
_cache_mode: i64,
|
||||
) -> Variant {
|
||||
let datafile_path = convert_path(&path);
|
||||
if let Some(resource) = self.retrieve_cache::<Resource>(format!(
|
||||
"{}.{}",
|
||||
datafile_path,
|
||||
if datafile_path.ends_with(".xml") || datafile_path.ends_with("dat") {
|
||||
"scn"
|
||||
} else {
|
||||
"res"
|
||||
}
|
||||
)) {
|
||||
return resource.to_variant();
|
||||
}
|
||||
|
||||
if let Some(target) = self.datafile_table.get(datafile_path.as_str()) {
|
||||
let mut file = File::open(DAT_PATH).unwrap();
|
||||
match load_data(target, &mut file) {
|
||||
Ok(DatafileFile::Level(level)) => {
|
||||
let level_id = datafile_path
|
||||
.split_terminator('\\')
|
||||
.find(|i| i.starts_with("level"))
|
||||
.map(|lvl| u32::from_str(lvl.strip_prefix("level").unwrap()).unwrap())
|
||||
.unwrap();
|
||||
let tile_map = create_tile_map(level, level_id);
|
||||
|
||||
self.save_to_cache(tile_map.share().upcast(), format!("{}.scn", datafile_path));
|
||||
tile_map.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Txt(txt)) => {
|
||||
let game_object = parse_game_object(txt);
|
||||
self.save_to_cache(
|
||||
game_object.share().upcast(),
|
||||
format!("{}.res", datafile_path),
|
||||
);
|
||||
game_object.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Ui(ui)) => {
|
||||
let ui = convert_ui(ui);
|
||||
let mut scene = PackedScene::new();
|
||||
scene.pack(ui.upcast());
|
||||
|
||||
self.save_to_cache(scene.share().upcast(), format!("{}.scn", datafile_path));
|
||||
scene.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Vorbis(vorbis)) => {
|
||||
let mut audio = AudioStreamOggVorbis::new();
|
||||
audio.set_loop(true);
|
||||
let mut packet = OggPacketSequence::new();
|
||||
packet.set_packet_data(Array::from(&[Array::from(&[PackedByteArray::from(
|
||||
vorbis.as_slice(),
|
||||
)
|
||||
.to_variant()])]));
|
||||
audio.set_packet_sequence(packet);
|
||||
audio.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::RleSprite(rle)) => load_rle_as_sprite_frames(*rle).to_variant(),
|
||||
Ok(DatafileFile::Sprites(sprites)) => {
|
||||
let sprite_frames = load_sprite_frames(sprites, path);
|
||||
|
||||
self.save_to_cache(
|
||||
sprite_frames.share().upcast(),
|
||||
format!("{}.res", datafile_path),
|
||||
);
|
||||
sprite_frames.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Bitmap(data)) => {
|
||||
let gd_image = match load_bmp_as_image_texture(data) {
|
||||
Ok(image) => image,
|
||||
Err(err) => return err.to_variant(),
|
||||
};
|
||||
|
||||
if datafile_path.contains("\\fonts\\") {
|
||||
let font = load_bitmap_font(gd_image);
|
||||
|
||||
self.save_to_cache(
|
||||
font.share().upcast(),
|
||||
format!("{}.tres", datafile_path),
|
||||
);
|
||||
font.to_variant()
|
||||
} else {
|
||||
let mut texture = ImageTexture::new();
|
||||
texture.set_image(gd_image);
|
||||
|
||||
self.save_to_cache(
|
||||
texture.share().upcast(),
|
||||
format!("{}.res", datafile_path),
|
||||
);
|
||||
texture.to_variant()
|
||||
}
|
||||
}
|
||||
Ok(DatafileFile::TileCollision(collision)) => {
|
||||
let tile_collision = Gd::new(TileCollision {
|
||||
collision: collision
|
||||
.chars()
|
||||
.filter_map(|c| c.to_digit(10))
|
||||
.map(|d| d as u8)
|
||||
.collect(),
|
||||
});
|
||||
|
||||
// No need to save this to cache, we only use this internally
|
||||
/*self.save_to_cache(
|
||||
tile_collision.share().upcast(),
|
||||
format!("{}.res", datafile_path),
|
||||
);*/
|
||||
tile_collision.to_variant()
|
||||
}
|
||||
Err(formats::Error::UnknownFormat(ext)) => {
|
||||
printerr(format!("Unknown format <{}>", ext).to_variant(), &[]);
|
||||
Error::ERR_FILE_UNRECOGNIZED.to_variant()
|
||||
}
|
||||
Err(formats::Error::Deserialization) => {
|
||||
printerr("Failed to deserialize".to_variant(), &[]);
|
||||
Error::ERR_FILE_CORRUPT.to_variant()
|
||||
}
|
||||
Err(formats::Error::Custom(message)) => {
|
||||
printerr(message.to_variant(), &[]);
|
||||
Error::ERR_BUG.to_variant()
|
||||
}
|
||||
_ => {
|
||||
printerr("Unknown error".to_variant(), &[]);
|
||||
Error::ERR_BUG.to_variant()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printerr("File not found".to_variant(), &[]);
|
||||
Error::ERR_FILE_NOT_FOUND.to_variant()
|
||||
}
|
||||
}
|
||||
}
|
||||
88
rust/src/godot/font.rs
Normal file
88
rust/src/godot/font.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use godot::builtin::{Rect2, Vector2, Vector2i};
|
||||
use godot::engine::{FontFile, Image};
|
||||
use godot::prelude::utilities::{print_verbose, prints};
|
||||
use godot::prelude::{Color, Gd, Share, ToVariant};
|
||||
use std::ops::Index;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
const CHARSET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß0123456789,;.:!?\
|
||||
+-*/=<>()[]{}\"$%&#~_’^@|¡¿™©®º¹²³ªÀÁÂÃÅÆÇÈÉÊËÌÍÎÏIÐGÑÒÓÔÕŒØSŠÙÚÛÝÞŸŽàáâãåæçèéêëìíî\
|
||||
ïiðgñòóôõœøsšùúûýþÿž£¥ƒ¤¯¦¬¸¨·§×¢±÷µ«»";
|
||||
|
||||
pub fn load_bitmap_font(image: Gd<Image>) -> Gd<FontFile> {
|
||||
let mut font_chars = CHARSET.as_bytes().iter();
|
||||
|
||||
let mut font_file = FontFile::new();
|
||||
|
||||
let chroma_key = Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.0,
|
||||
};
|
||||
|
||||
let mut was_empty_column = true;
|
||||
let mut char_x = 0;
|
||||
let mut char_width = 0;
|
||||
let char_height = image.get_height();
|
||||
let char_y = 0;
|
||||
|
||||
let base_size = Vector2i { x: 16, y: 0 };
|
||||
|
||||
font_file.set_texture_image(0, base_size, 0, image.share());
|
||||
|
||||
for x in 0..image.get_width() {
|
||||
let is_empty_column = (0..image.get_height()).all(|y| image.get_pixel(x, y).a == 0.0);
|
||||
|
||||
if !was_empty_column && is_empty_column {
|
||||
let char = font_chars.next().expect("Font has too many characters!");
|
||||
let glyph = *char as i64;
|
||||
/*let mut glyph = 0i64;
|
||||
for (i, c) in char.bytes().rev().enumerate() {
|
||||
glyph |= (c as i64) << (i * 8);
|
||||
}*/
|
||||
|
||||
let glyph_offset = Vector2 {
|
||||
x: char_x as f32,
|
||||
y: char_y as f32,
|
||||
};
|
||||
let glyph_size = Vector2 {
|
||||
x: char_width as f32,
|
||||
y: char_height as f32,
|
||||
};
|
||||
|
||||
prints(
|
||||
"Glyph".to_variant(),
|
||||
&[
|
||||
(*char as char).to_string().to_variant(),
|
||||
glyph_offset.to_variant(),
|
||||
glyph_size.to_variant(),
|
||||
],
|
||||
);
|
||||
|
||||
// font_file.set_glyph_offset(0, base_size, glyph, glyph_offset);
|
||||
font_file.set_glyph_size(0, base_size, glyph, glyph_size);
|
||||
font_file.set_glyph_uv_rect(
|
||||
0,
|
||||
base_size,
|
||||
glyph,
|
||||
Rect2 {
|
||||
position: glyph_offset,
|
||||
size: glyph_size,
|
||||
},
|
||||
);
|
||||
font_file.set_glyph_texture_idx(0, base_size, glyph, 0);
|
||||
} else if was_empty_column && !is_empty_column {
|
||||
char_x = x;
|
||||
char_width = 0;
|
||||
}
|
||||
|
||||
char_width += 1;
|
||||
was_empty_column = is_empty_column;
|
||||
}
|
||||
|
||||
font_file.set_font_name("menufont".into());
|
||||
// font_file.set_cache_ascent(0, base_size.x, )
|
||||
|
||||
font_file
|
||||
}
|
||||
138
rust/src/godot/game_object.rs
Normal file
138
rust/src/godot/game_object.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use godot::engine::Resource;
|
||||
use godot::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Resource, init)]
|
||||
pub struct ObjectScript {
|
||||
#[export]
|
||||
pub dynamic_objects: Array<Gd<ObjectData>>,
|
||||
#[export]
|
||||
pub static_objects: Array<Gd<ObjectData>>,
|
||||
#[base]
|
||||
base: Base<Resource>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ObjectScript {}
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Resource, init)]
|
||||
pub struct ObjectData {
|
||||
#[export]
|
||||
pub class_type: GodotString,
|
||||
#[export]
|
||||
pub resource_type: GodotString,
|
||||
#[export]
|
||||
pub name: GodotString,
|
||||
#[export]
|
||||
pub props: Dictionary,
|
||||
#[export]
|
||||
pub children: Array<Gd<ObjectData>>,
|
||||
#[base]
|
||||
base: Base<Resource>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ObjectData {}
|
||||
|
||||
pub fn parse_game_object(contents: String) -> Gd<ObjectScript> {
|
||||
Gd::<ObjectScript>::with_base(|base| {
|
||||
let mut object_script = ObjectScript {
|
||||
dynamic_objects: Array::new(),
|
||||
static_objects: Array::new(),
|
||||
base,
|
||||
};
|
||||
|
||||
let mut lines = contents
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.filter(|l| !l.starts_with('#'));
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
match line {
|
||||
"DYNAMIC OBJECT START" => {
|
||||
object_script.dynamic_objects.push(read_object(&mut lines))
|
||||
}
|
||||
"OBJECT START" => object_script.static_objects.push(read_object(&mut lines)),
|
||||
l => eprintln!("TODO: {}", l),
|
||||
};
|
||||
}
|
||||
|
||||
object_script
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_object<'s, I>(lines: &mut I) -> Gd<ObjectData>
|
||||
where
|
||||
I: Iterator<Item = &'s str>,
|
||||
{
|
||||
let class_type = lines
|
||||
.next()
|
||||
.unwrap()
|
||||
.strip_prefix("class type:")
|
||||
.unwrap()
|
||||
.trim()
|
||||
.trim_matches('"');
|
||||
let (resource_type, name) = lines
|
||||
.next()
|
||||
.unwrap()
|
||||
.splitn(2, ']')
|
||||
.map(|x| x.trim())
|
||||
.collect_tuple::<(&str, &str)>()
|
||||
.unwrap();
|
||||
|
||||
Gd::<ObjectData>::with_base(|base| {
|
||||
let mut object_data = ObjectData {
|
||||
class_type: class_type.into(),
|
||||
resource_type: resource_type
|
||||
.trim_start_matches('[')
|
||||
.trim_end_matches(']')
|
||||
.into(),
|
||||
name: name.trim_matches('"').into(),
|
||||
props: Dictionary::new(),
|
||||
children: Array::new(),
|
||||
base,
|
||||
};
|
||||
|
||||
lines.next();
|
||||
loop {
|
||||
match lines.next().unwrap() {
|
||||
"}" => break,
|
||||
l => {
|
||||
let (_, key, value) = l
|
||||
.splitn(3, '"')
|
||||
.map(|x| x.trim())
|
||||
.collect_tuple::<(&str, &str, &str)>()
|
||||
.unwrap();
|
||||
let values = value
|
||||
.split_whitespace()
|
||||
.map(|s| f32::from_str(s).unwrap())
|
||||
.collect_vec();
|
||||
object_data.props.insert(
|
||||
key,
|
||||
match values.len() {
|
||||
1 => values[0].to_variant(),
|
||||
2 => Vector2 {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
}
|
||||
.to_variant(),
|
||||
3 => Vector3 {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
z: values[2],
|
||||
}
|
||||
.to_variant(),
|
||||
_ => panic!(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object_data
|
||||
})
|
||||
}
|
||||
57
rust/src/godot/image.rs
Normal file
57
rust/src/godot/image.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::formats::rle::RleImage;
|
||||
use godot::builtin::{Color, PackedByteArray};
|
||||
use godot::engine::global::Error;
|
||||
use godot::engine::image::Format;
|
||||
use godot::engine::{Image, ImageTexture, SpriteFrames};
|
||||
use godot::obj::Gd;
|
||||
|
||||
const FPS: f64 = 15.0;
|
||||
|
||||
pub fn load_rle_as_sprite_frames(rle: RleImage) -> Gd<SpriteFrames> {
|
||||
let mut frames = SpriteFrames::new();
|
||||
|
||||
frames.set_animation_loop("default".into(), true);
|
||||
frames.set_animation_speed("default".into(), FPS);
|
||||
|
||||
for frame in rle.frames.iter() {
|
||||
let mut image = Image::new();
|
||||
image.set_data(
|
||||
rle.width as i64,
|
||||
rle.height as i64,
|
||||
false,
|
||||
Format::FORMAT_RGBA8,
|
||||
PackedByteArray::from(rle.get_image_data(frame).as_slice()),
|
||||
);
|
||||
image.fix_alpha_edges();
|
||||
|
||||
let mut texture = ImageTexture::new();
|
||||
texture.set_image(image);
|
||||
frames.add_frame("default".into(), texture.upcast(), 1.0, 0);
|
||||
}
|
||||
|
||||
frames
|
||||
}
|
||||
|
||||
pub fn load_bmp_as_image_texture(data: Vec<u8>) -> Result<Gd<Image>, Error> {
|
||||
let mut image = Image::new();
|
||||
|
||||
match image.load_bmp_from_buffer(data.as_slice().into()) {
|
||||
Error::OK => {
|
||||
for x in 0..image.get_width() {
|
||||
for y in 0..image.get_height() {
|
||||
if image.get_pixel(x, y).is_equal_approx(Color {
|
||||
r: 1.0,
|
||||
g: 0.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
}) {
|
||||
image.set_pixel(x, y, Color::TRANSPARENT_BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
image.fix_alpha_edges();
|
||||
Ok(image)
|
||||
}
|
||||
error => Err(error),
|
||||
}
|
||||
}
|
||||
7
rust/src/godot/mod.rs
Normal file
7
rust/src/godot/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod datafile;
|
||||
pub mod font;
|
||||
pub mod game_object;
|
||||
pub mod image;
|
||||
pub mod sprites;
|
||||
pub mod tile_map;
|
||||
pub mod ui;
|
||||
133
rust/src/godot/sprites.rs
Normal file
133
rust/src/godot/sprites.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use crate::formats::sprites::{CropMode, RenderMode, Sprites};
|
||||
use godot::builtin::{GodotString, Rect2, StringName, ToVariant, Vector2};
|
||||
use godot::engine::utilities::printerr;
|
||||
use godot::engine::{
|
||||
load, AtlasTexture, ImageTexture, PlaceholderTexture2D, ResourceLoader, SpriteFrames,
|
||||
};
|
||||
use godot::obj::{Gd, Share};
|
||||
use godot::prelude::GodotClass;
|
||||
|
||||
const FPS: f64 = 15.0;
|
||||
const SPRITE_EXTENSIONS: &[&str] = &["bmp", "rle"];
|
||||
|
||||
pub fn load_sprite_frames(sprites: Vec<Sprites>, path: GodotString) -> Gd<SpriteFrames> {
|
||||
let dir = path
|
||||
.to_string()
|
||||
.strip_suffix("/sprites.txt")
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut sprite_frames = SpriteFrames::new();
|
||||
for sprite in sprites.into_iter() {
|
||||
if let RenderMode::FlipX = sprite.render_mode {
|
||||
continue;
|
||||
}
|
||||
sprite_frames.add_animation(StringName::from(&sprite.name));
|
||||
sprite_frames.set_animation_speed(StringName::from(&sprite.name), FPS);
|
||||
|
||||
match select_from_extensions(&dir, &sprite.file_name) {
|
||||
Some((path, "rle")) => extract_rle_frames(&mut sprite_frames, &sprite, path),
|
||||
Some((path, "bmp")) => extract_bitmap_frames(&mut sprite_frames, &sprite, path),
|
||||
Some(_) | None => {
|
||||
printerr(
|
||||
format!("Missing sprite '{}'", sprite.file_name).to_variant(),
|
||||
&[],
|
||||
);
|
||||
let texture = PlaceholderTexture2D::new();
|
||||
sprite_frames.add_frame(
|
||||
StringName::from(&sprite.name),
|
||||
texture.upcast(),
|
||||
60.0 / FPS,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sprite_frames
|
||||
}
|
||||
|
||||
/// Loads an RLE file as SpriteFrames and extracts
|
||||
/// its frames into `sprite_frames`
|
||||
fn extract_rle_frames(sprite_frames: &mut SpriteFrames, sprite: &Sprites, path: String) {
|
||||
let frames: Gd<SpriteFrames> = load(path);
|
||||
for frame_idx in 0..frames.get_frame_count("default".into()) {
|
||||
sprite_frames.add_frame(
|
||||
StringName::from(&sprite.name),
|
||||
frames
|
||||
.get_frame_texture("default".into(), frame_idx)
|
||||
.unwrap(),
|
||||
60.0 / FPS,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a bitmap and extracts its frames into `sprite_frames`
|
||||
/// creates an atlas if there are multiple frames.
|
||||
fn extract_bitmap_frames(sprite_frames: &mut SpriteFrames, sprite: &Sprites, path: String) {
|
||||
let texture: Gd<ImageTexture> = load(path);
|
||||
|
||||
let frame_count = if let Some(CropMode::FrameCount(frame_count)) = sprite.frames {
|
||||
frame_count
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
if frame_count > 1 {
|
||||
let height = texture.get_height();
|
||||
let width = texture.get_width();
|
||||
let frame_height = height / frame_count as i64;
|
||||
|
||||
for i in 0..frame_count as i64 {
|
||||
let mut atlas = AtlasTexture::new();
|
||||
atlas.set_atlas(texture.share().upcast());
|
||||
atlas.set_region(Rect2 {
|
||||
position: Vector2 {
|
||||
x: 0.0,
|
||||
y: (i * frame_height) as f32,
|
||||
},
|
||||
size: Vector2 {
|
||||
x: width as f32,
|
||||
y: frame_height as f32,
|
||||
},
|
||||
});
|
||||
|
||||
sprite_frames.add_frame(
|
||||
StringName::from(&sprite.name),
|
||||
atlas.upcast(),
|
||||
60.0 / FPS,
|
||||
0,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sprite_frames.add_frame(
|
||||
StringName::from(&sprite.name),
|
||||
texture.upcast(),
|
||||
60.0 / FPS,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects the extension based on which file exists
|
||||
fn select_from_extensions(dir: &str, file_name: &str) -> Option<(String, &'static str)> {
|
||||
SPRITE_EXTENSIONS
|
||||
.iter()
|
||||
.map(|ext| {
|
||||
(
|
||||
format!("{}/sprites/{}.{}", dir, file_name.to_lowercase(), ext),
|
||||
*ext,
|
||||
)
|
||||
})
|
||||
.find(|(path, ext)| {
|
||||
ResourceLoader::singleton().exists(
|
||||
path.clone().into(),
|
||||
match *ext {
|
||||
"rle" => SpriteFrames::CLASS_NAME.to_string(),
|
||||
"bmp" => ImageTexture::CLASS_NAME.to_string(),
|
||||
_ => panic!(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
}
|
||||
139
rust/src/godot/tile_map.rs
Normal file
139
rust/src/godot/tile_map.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::formats::level::LevelLayer;
|
||||
use godot::engine::global::Error;
|
||||
use godot::engine::utilities::{clampi, printerr};
|
||||
use godot::engine::{load, PackedScene};
|
||||
use godot::engine::{ImageTexture, TileSet};
|
||||
use godot::engine::{TileMap, TileSetAtlasSource};
|
||||
use godot::prelude::*;
|
||||
use godot::prelude::{Gd, PackedByteArray, Share, ToVariant};
|
||||
|
||||
pub fn create_tile_map(layer: LevelLayer, level_id: u32) -> Gd<PackedScene> {
|
||||
let mut tile_set = TileSet::new();
|
||||
tile_set.set_tile_size(Vector2i { x: 32, y: 32 });
|
||||
tile_set.add_physics_layer(0);
|
||||
let mut map = TileMap::new_alloc();
|
||||
map.set_tileset(tile_set.share());
|
||||
map.set_quadrant_size(32);
|
||||
|
||||
for x in 0..layer.width {
|
||||
for y in 0..layer.height {
|
||||
let tile = &layer.tiles[(y * layer.width + x) as usize];
|
||||
if tile.id == 0 {
|
||||
continue;
|
||||
}
|
||||
if !tile_set.has_source(tile.id as i64) {
|
||||
let atlas_id = tile.id as u32 + 1;
|
||||
let atlas = load_atlas(1, atlas_id, layer.tile_count);
|
||||
tile_set.add_source(atlas.share().upcast(), tile.id as i64);
|
||||
add_collision(atlas, level_id, atlas_id);
|
||||
}
|
||||
map.set_cell(
|
||||
0,
|
||||
Vector2i {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
},
|
||||
tile.id as i64,
|
||||
Vector2i {
|
||||
x: clampi(tile.index as i64 % 16, 0, 15) as i32,
|
||||
y: clampi(tile.index as i64 / 16, 0, 15) as i32,
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut scene = PackedScene::new();
|
||||
let error = scene.pack(map.upcast());
|
||||
match error {
|
||||
Error::OK => (),
|
||||
e => printerr(e.to_variant(), &[]),
|
||||
}
|
||||
scene
|
||||
}
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Resource, init)]
|
||||
pub struct TileCollision {
|
||||
#[export]
|
||||
pub collision: PackedByteArray,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl TileCollision {}
|
||||
|
||||
fn add_collision(atlas: Gd<TileSetAtlasSource>, level_id: u32, atlas_id: u32) {
|
||||
let tile_collision: Gd<TileCollision> = load(format!(
|
||||
"datafile://data/level{:02}/tile_collision_{:02}.txt",
|
||||
level_id, atlas_id
|
||||
));
|
||||
let width = atlas.get_atlas_grid_size().x;
|
||||
let height = atlas.get_atlas_grid_size().y;
|
||||
|
||||
let tile_width = atlas.get_texture_region_size().x as f32 / 2.0;
|
||||
let tile_height = atlas.get_texture_region_size().y as f32 / 2.0;
|
||||
let collision = &[
|
||||
Vector2 {
|
||||
x: -tile_width,
|
||||
y: -tile_height,
|
||||
},
|
||||
Vector2 {
|
||||
x: -tile_width,
|
||||
y: tile_height,
|
||||
},
|
||||
Vector2 {
|
||||
x: tile_width,
|
||||
y: tile_height,
|
||||
},
|
||||
Vector2 {
|
||||
x: tile_width,
|
||||
y: -tile_height,
|
||||
},
|
||||
];
|
||||
|
||||
for x in 0..width {
|
||||
for y in 0..height {
|
||||
let collision_data = tile_collision
|
||||
.bind()
|
||||
.collision
|
||||
.get((y * width + x) as usize);
|
||||
let mut data = atlas.get_tile_data(Vector2i { x, y }, 0).unwrap();
|
||||
if collision_data & 0x1 != 0 {
|
||||
data.add_collision_polygon(0);
|
||||
data.set_collision_polygon_points(0, 0, PackedVector2Array::from(collision));
|
||||
} else if collision_data & 0xfe != 0 {
|
||||
printerr(
|
||||
format!("Missing collision info for {}", collision_data).to_variant(),
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_atlas(set_id: u32, atlas_id: u32, tile_count: u32) -> Gd<TileSetAtlasSource> {
|
||||
let mut atlas = TileSetAtlasSource::new();
|
||||
let tex: Gd<ImageTexture> = load(format!(
|
||||
"datafile://data/set{}/sprites/tiles_{:02}.bmp",
|
||||
set_id, atlas_id,
|
||||
));
|
||||
let region_size = (tile_count as f32).sqrt();
|
||||
debug_assert_eq!(tex.get_width(), tex.get_height());
|
||||
debug_assert_eq!(region_size, region_size.trunc());
|
||||
|
||||
let tile_size = (tex.get_width() / region_size as i64) as i32;
|
||||
|
||||
atlas.set_texture(tex.upcast());
|
||||
atlas.set_texture_region_size(Vector2i {
|
||||
x: tile_size,
|
||||
y: tile_size,
|
||||
});
|
||||
|
||||
for x in 0..region_size as i32 {
|
||||
for y in 0..region_size as i32 {
|
||||
atlas.create_tile(Vector2i { x, y }, Vector2i { x: 1, y: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
atlas
|
||||
}
|
||||
58
rust/src/godot/ui.rs
Normal file
58
rust/src/godot/ui.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::formats::ui_xml::{HorizontalAlign, UiTag};
|
||||
use godot::builtin::{GodotString, Vector2};
|
||||
use godot::engine::global::HorizontalAlignment;
|
||||
use godot::engine::node::InternalMode;
|
||||
use godot::engine::{Button, Container, Control, TextureRect};
|
||||
use godot::prelude::*;
|
||||
|
||||
pub fn convert_ui(ui: UiTag) -> Gd<Control> {
|
||||
match ui {
|
||||
UiTag::Menu(menu) => {
|
||||
let mut gd_menu = Container::new_alloc();
|
||||
for child in menu.children {
|
||||
gd_menu.add_child(
|
||||
convert_ui(child).upcast(),
|
||||
false,
|
||||
InternalMode::INTERNAL_MODE_FRONT,
|
||||
);
|
||||
}
|
||||
gd_menu.upcast()
|
||||
}
|
||||
UiTag::Image(image) => {
|
||||
let mut gd_image = TextureRect::new_alloc();
|
||||
gd_image.set_position(
|
||||
Vector2 {
|
||||
x: image.position[0] as f32,
|
||||
y: image.position[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_image.set_size(
|
||||
Vector2 {
|
||||
x: image.size[0] as f32,
|
||||
y: image.size[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_image.upcast()
|
||||
}
|
||||
UiTag::TextButton(button) => {
|
||||
let mut gd_button = Button::new_alloc();
|
||||
gd_button.set_position(
|
||||
Vector2 {
|
||||
x: button.position[0] as f32,
|
||||
y: button.position[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_button.set_text_alignment(match button.horizontal_align {
|
||||
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
|
||||
});
|
||||
if let Some(name) = button.name {
|
||||
gd_button.set_name(GodotString::from(name));
|
||||
}
|
||||
gd_button.set_text(GodotString::from(button.text));
|
||||
gd_button.upcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
40
rust/src/lib.rs
Normal file
40
rust/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::godot::datafile::DatafileLoader;
|
||||
use ::godot::engine::class_macros::auto_register_classes;
|
||||
use ::godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
|
||||
use ::godot::init::{gdextension, ExtensionLayer};
|
||||
use ::godot::prelude::{ExtensionLibrary, Gd, InitHandle, InitLevel, Share};
|
||||
|
||||
pub mod formats;
|
||||
pub mod godot;
|
||||
|
||||
struct Main {}
|
||||
|
||||
#[gdextension]
|
||||
unsafe impl ExtensionLibrary for Main {
|
||||
fn load_library(handle: &mut InitHandle) -> bool {
|
||||
handle.register_layer(InitLevel::Editor, ResourceLoaderLayer { datafile: None });
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceLoaderLayer {
|
||||
pub datafile: Option<Gd<DatafileLoader>>,
|
||||
}
|
||||
|
||||
impl ExtensionLayer for ResourceLoaderLayer {
|
||||
fn initialize(&mut self) {
|
||||
auto_register_classes();
|
||||
|
||||
self.datafile = Some(Gd::<DatafileLoader>::with_base(DatafileLoader::init));
|
||||
|
||||
ResourceLoader::singleton()
|
||||
.add_resource_format_loader(self.datafile.as_ref().unwrap().share().upcast(), true);
|
||||
}
|
||||
|
||||
fn deinitialize(&mut self) {
|
||||
if let Some(datafile) = &self.datafile {
|
||||
ResourceLoader::singleton().remove_resource_format_loader(datafile.share().upcast());
|
||||
self.datafile = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
rust/src/main.rs
Normal file
130
rust/src/main.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use binrw::{BinRead, NullString};
|
||||
use image::codecs::gif::{GifEncoder, Repeat};
|
||||
use image::{AnimationDecoder, ImageFormat};
|
||||
use mhjnr::formats::datafile::Datafile;
|
||||
use mhjnr::formats::level::level_tile_data_to_image;
|
||||
use mhjnr::formats::rle::RleImage;
|
||||
use mhjnr::formats::sprites::Sprites;
|
||||
use mhjnr::formats::txt::{decrypt_exposed_txt, decrypt_txt};
|
||||
use mhjnr::formats::ui_xml::UiTag;
|
||||
use serde_xml_rs::from_str;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
fn extract(datafile: &Datafile, file: &mut File) {
|
||||
let target = "E:\\Games\\Schatzjäger\\data3";
|
||||
|
||||
for entry in &datafile.files {
|
||||
let file_name = format!("{}\\{}", target, entry.name);
|
||||
fs::create_dir_all(file_name.rsplit_once('\\').unwrap().0).unwrap();
|
||||
|
||||
file.seek(SeekFrom::Start(entry.pos as u64)).unwrap();
|
||||
let mut data = vec![0u8; entry.len as usize];
|
||||
file.read_exact(&mut data).unwrap();
|
||||
|
||||
if entry.name.to_string().ends_with(".txt") {
|
||||
let mut contents = decrypt_txt(data.into_iter()).unwrap();
|
||||
/*if entry
|
||||
.name
|
||||
.to_string()
|
||||
.split('\\')
|
||||
.collect::<Vec<&str>>()
|
||||
.len()
|
||||
== 1
|
||||
{
|
||||
contents = decrypt_exposed_txt(contents).unwrap();
|
||||
}*/
|
||||
File::create(file_name)
|
||||
.unwrap()
|
||||
.write_all(contents.as_bytes())
|
||||
.unwrap();
|
||||
} else if entry.name.to_string().ends_with(".rle") {
|
||||
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
|
||||
let mut encoder = GifEncoder::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(format!(
|
||||
"{}.{}",
|
||||
file_name.strip_suffix(".rle").unwrap(),
|
||||
".gif"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
encoder.set_repeat(Repeat::Infinite).unwrap();
|
||||
encoder.try_encode_frames(image.into_frames()).unwrap();
|
||||
} else {
|
||||
File::create(file_name)
|
||||
.unwrap()
|
||||
.write_all(data.as_slice())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let file_name = Some(NullString::from("data\\loading\\sprites.txt"));
|
||||
let dat_path = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
|
||||
|
||||
let mut file = File::open(dat_path).unwrap();
|
||||
let dat: Datafile = Datafile::read(&mut file).unwrap();
|
||||
println!("{:#?}", dat);
|
||||
|
||||
extract(&dat, &mut file);
|
||||
|
||||
/*if let Some(file_name) = file_name {
|
||||
let target = dat.files.iter().find(|it| it.name == file_name).unwrap();
|
||||
file.seek(SeekFrom::Start(target.pos as u64)).unwrap();
|
||||
let mut data = vec![0u8; target.len as usize];
|
||||
file.read_exact(&mut data).unwrap();
|
||||
|
||||
match Path::new(&file_name.to_string())
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
{
|
||||
Some("xml") => println!(
|
||||
"{:#?}",
|
||||
from_str::<UiTag>(String::from_utf8(data).unwrap().as_str())
|
||||
),
|
||||
Some("txt") => {
|
||||
if false {
|
||||
/*let decr = decrypt_txt(&mut data);
|
||||
let entries: String = decrypt_exposed_txt(decr);*/
|
||||
let decr = decrypt_txt(data.into_iter()).unwrap();
|
||||
println!("{}", &decr);
|
||||
let sprites = Sprites::parse(decr.as_str()).unwrap();
|
||||
println!("{:#?}", sprites);
|
||||
} else {
|
||||
println!("{}", decrypt_txt(data.into_iter()).unwrap())
|
||||
}
|
||||
}
|
||||
Some("rle") => {
|
||||
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.gif");
|
||||
println!("{:?}", path);
|
||||
let mut encoder = GifEncoder::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.unwrap(),
|
||||
);
|
||||
encoder.set_repeat(Repeat::Infinite).unwrap();
|
||||
encoder.try_encode_frames(image.into_frames()).unwrap();
|
||||
}
|
||||
Some("dat") => {
|
||||
let image = level_tile_data_to_image(&data).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.png");
|
||||
println!("{:?}", path);
|
||||
image.save_with_format(path, ImageFormat::Png).unwrap();
|
||||
}
|
||||
Some(ext) => eprintln!("Unknown file extension <{}>", ext),
|
||||
None => eprintln!("Failed to read"),
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// pub fn decr2()
|
||||
Reference in New Issue
Block a user