initial commit

This commit is contained in:
2023-05-04 00:32:54 +02:00
commit 44b4d847e5
34 changed files with 3510 additions and 0 deletions

3
rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
.idea
mhjnr.iml

832
rust/Cargo.lock generated Normal file
View 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
View 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" }

View 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
View 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
View 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
View 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,
))
}

View 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
View 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 {}

View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()