diff --git a/rust/Cargo.lock b/rust/Cargo.lock index dc29136..9d12b25 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,12 +2,24 @@ # 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 = "binrw" version = "0.11.1" @@ -32,24 +44,290 @@ dependencies = [ "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 = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[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 = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[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.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[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" @@ -59,12 +337,128 @@ 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 = "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 = "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 = "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" @@ -74,6 +468,15 @@ 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" @@ -83,10 +486,38 @@ 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 = "renderwarelib" version = "0.1.0" +[[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.162" @@ -119,11 +550,35 @@ dependencies = [ "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 = "springylib" version = "0.1.0" dependencies = [ "binrw", + "encoding_rs", + "image", + "itertools", "serde", "serde-xml-rs", ] @@ -170,14 +625,100 @@ dependencies = [ "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 = "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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40e1a60e6e271dfb0db2db95987551348db75ebd6a78be07b9039b036f7dae2a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/rust/springylib/Cargo.toml b/rust/springylib/Cargo.toml index 5cb5e9d..86e7536 100644 --- a/rust/springylib/Cargo.toml +++ b/rust/springylib/Cargo.toml @@ -5,5 +5,11 @@ edition = "2021" [dependencies] binrw = "0.11.1" +image = {version = "0.24.6", optional = true} serde = {version = "1.0.160", features = ["derive"]} +encoding_rs = "0.8.32" +itertools = "0.10.5" serde-xml-rs = "0.6.0" + +[features] +rle_gif = ["dep:image"] diff --git a/rust/springylib/README.md b/rust/springylib/README.md index cedd3e6..a00a226 100644 --- a/rust/springylib/README.md +++ b/rust/springylib/README.md @@ -1,3 +1,15 @@ # SpringyLib A library for reading data from the Sproing Engine. + +## Components + +* [x] Archive + * [x] V1 + * [x] V2 +* [ ] Media + * [x] RLE Sprites + * [x] XML UI + * [ ] TXT Object Serialization + * [ ] TXT Animation Sets + * [ ] TXT Sprite Sets diff --git a/rust/springylib/src/archive/mod.rs b/rust/springylib/src/archive/mod.rs index 18feca9..0d913e3 100644 --- a/rust/springylib/src/archive/mod.rs +++ b/rust/springylib/src/archive/mod.rs @@ -18,6 +18,7 @@ pub struct Archive(HashMap); pub struct FilePointer { pub position: usize, pub length: usize, + pub path: String, } impl Deref for Archive { @@ -114,7 +115,8 @@ mod tests { archive["data\\config.txt"], FilePointer { position: 0x57b40, - length: 0xf4 + length: 0xf4, + path: "data\\config.txt".to_string(), } ); assert_eq!( @@ -122,6 +124,7 @@ mod tests { FilePointer { position: 0x57c40, length: 0x7dfd8, + path: "data\\fonts\\dangerfont.bmp".to_string() } ) } @@ -136,6 +139,7 @@ mod tests { FilePointer { position: 0x1200, length: 0x8d9, + path: "data\\mhx.fnt".to_string() } ); assert_eq!( @@ -143,6 +147,7 @@ mod tests { FilePointer { position: 0x1c00, length: 0x427e, + path: "data\\text.txt".to_string(), } ) } @@ -157,6 +162,7 @@ mod tests { FilePointer { position: 0x7000, length: 0x40, + path: "data\\endbranding_xxl.txt".to_string() } ); assert_eq!( @@ -164,6 +170,7 @@ mod tests { FilePointer { position: 0x7200, length: 0x872, + path: "data\\settings_xxl.txt".to_string(), } ) } diff --git a/rust/springylib/src/archive/v1.rs b/rust/springylib/src/archive/v1.rs index ca1775c..a363007 100644 --- a/rust/springylib/src/archive/v1.rs +++ b/rust/springylib/src/archive/v1.rs @@ -47,6 +47,7 @@ impl From for FilePointer { FilePointer { position: value.pointer[0] as usize, length: value.pointer[1] as usize, + path: value.name.to_string(), } } } diff --git a/rust/springylib/src/archive/v2.rs b/rust/springylib/src/archive/v2.rs index e32bea1..0788944 100644 --- a/rust/springylib/src/archive/v2.rs +++ b/rust/springylib/src/archive/v2.rs @@ -43,6 +43,7 @@ impl From for FilePointer { FilePointer { position: value.pos as usize, length: value.len as usize, + path: value.name.to_string(), } } } diff --git a/rust/springylib/src/error.rs b/rust/springylib/src/error.rs new file mode 100644 index 0000000..294379a --- /dev/null +++ b/rust/springylib/src/error.rs @@ -0,0 +1,66 @@ +use crate::media::txt::DecryptError; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum Error { + UnknownFormat(String), + InvalidExtension(Option), + InvalidPath(String), + InvalidData { + info: Option, + context: String, + }, + Custom(Box), + UnknownError, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::UnknownFormat(format) => write!(f, "Unknown format: {}", format), + Error::UnknownError => write!(f, "Unknown Error"), + Error::InvalidExtension(None) => write!(f, "Missing file extension"), + Error::InvalidExtension(Some(ext)) => write!(f, "Invalid extension {}", ext), + Error::InvalidPath(path) => write!(f, "Invalid Path {}", path), + Error::InvalidData { info, context } => write!( + f, + "Invalid data: {}; {}", + info.unwrap_or("[no info]".to_string()), + context + ), + Error::Custom(error) => write!(f, "{}", error), + } + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(value: binrw::Error) -> Self { + Error::Custom(Box::new(value)) + } +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Error::Custom(Box::new(value)) + } +} + +impl From for Error { + fn from(value: serde_xml_rs::Error) -> Self { + Error::Custom(Box::new(value)) + } +} + +impl From for Error { + fn from(value: std::string::FromUtf8Error) -> Self { + Error::Custom(Box::new(value)) + } +} + +impl From for Error { + fn from(value: DecryptError) -> Self { + Error::Custom(Box::new(value)) + } +} diff --git a/rust/springylib/src/lib.rs b/rust/springylib/src/lib.rs index 4637171..aacda58 100644 --- a/rust/springylib/src/lib.rs +++ b/rust/springylib/src/lib.rs @@ -1,2 +1,92 @@ +use crate::archive::FilePointer; +use crate::error::Error; +use crate::media::level::LevelLayer; +use crate::media::rle::RleImage; +use crate::media::sprites::Sprites; +use crate::media::txt::{decrypt_exposed_txt, decrypt_txt}; +use crate::media::ui::UiTag; +use binrw::prelude::BinRead; +use encoding_rs::WINDOWS_1252; +use itertools::Itertools; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::io::{Cursor, Read, Seek, SeekFrom}; +use std::path::Path; + pub mod archive; +pub mod error; pub mod media; + +pub enum DatafileFile { + Txt(String), + Level(LevelLayer), + Sprites(Vec), + RleSprite(Box), + Bitmap(Vec), + Vorbis(Vec), + TileCollision(String), + Ui(UiTag), + Translations(HashMap>), +} + +impl FilePointer { + pub fn load_from(&self, reader: &mut R) -> Result + where + R: Read + Seek, + { + reader.seek(SeekFrom::Start(self.position as u64))?; + let mut data = vec![0u8; self.length as usize]; + reader.read_exact(&mut data)?; + let path = Path::new(&self.path); + + match path + .extension() + .and_then(OsStr::to_str) + .ok_or(Error::InvalidExtension(None))? + { + "dat" => Ok(DatafileFile::Level(LevelLayer::read(&mut Cursor::new( + data, + ))?)), + "rle" => Ok(DatafileFile::RleSprite(Box::new(RleImage::read( + &mut Cursor::new(data), + )?))), + "bmp" => Ok(DatafileFile::Bitmap(data)), + "ogg" => Ok(DatafileFile::Vorbis(data)), + "xml" => Ok(DatafileFile::Ui( + serde_xml_rs::from_str::(String::from_utf8(data)?.as_str())?.post_process(), + )), + "txt" => { + let stem = path + .file_stem() + .and_then(OsStr::to_str) + .ok_or_else(|| Error::InvalidPath(path.to_string_lossy().to_string()))?; + let decr = decrypt_txt(data.into_iter())?; + if stem.starts_with("tile_collision") { + Ok(DatafileFile::TileCollision(decr)) + } else if stem == "sprites" { + Ok(DatafileFile::Sprites(Sprites::parse(decr.as_str())?)) + } else if stem.starts_with("profile") || stem.starts_with("highscores") { + Ok(DatafileFile::Txt(decrypt_exposed_txt(decr)?)) + } else { + Ok(DatafileFile::Txt(decr)) + } + } + "csv" => Ok(DatafileFile::Translations( + WINDOWS_1252 + .decode(data.as_slice()) + .0 + .split('\n') + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .map(|l| { + l.splitn(2, ';') + .map(|s| s.to_string()) + .collect_tuple::<(String, String)>() + .expect("Invalid csv") + }) + .into_group_map(), + )), + ext => Err(Error::UnknownFormat(ext.to_string())), + } + } +} diff --git a/rust/springylib/src/media/font/charset-utf8.txt b/rust/springylib/src/media/font/charset-utf8.txt new file mode 100644 index 0000000..4f1d0ac --- /dev/null +++ b/rust/springylib/src/media/font/charset-utf8.txt @@ -0,0 +1 @@ +ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß0123456789,;.:!?+-*/=<>()[]{}\"$%&#~_’^@|¡¿™©®º¹²³ªÀÁÂÃÅÆÇÈÉÊËÌÍÎÏIÐGÑÒÓÔÕŒØSŠÙÚÛÝÞŸŽàáâãåæçèéêëìíîïiðgñòóôõœøsšùúûýþÿž£¥ƒ¤¯¦¬¸¨·§×¢±÷µ«» \ No newline at end of file diff --git a/rust/src/godot/charset.txt b/rust/springylib/src/media/font/charset.txt similarity index 100% rename from rust/src/godot/charset.txt rename to rust/springylib/src/media/font/charset.txt diff --git a/rust/springylib/src/media/font/mod.rs b/rust/springylib/src/media/font/mod.rs new file mode 100644 index 0000000..6d0832f --- /dev/null +++ b/rust/springylib/src/media/font/mod.rs @@ -0,0 +1,2 @@ +pub const CHARSET: &[u8] = include_bytes!("charset.txt"); +pub const CHARSET_UTF8: &str = include_str!("charset-utf8.txt"); diff --git a/rust/springylib/src/media/level.rs b/rust/springylib/src/media/level.rs new file mode 100644 index 0000000..df34ba7 --- /dev/null +++ b/rust/springylib/src/media/level.rs @@ -0,0 +1,19 @@ +use binrw::prelude::*; + +#[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, +} diff --git a/rust/springylib/src/media/mod.rs b/rust/springylib/src/media/mod.rs index 6bae95d..f12533e 100644 --- a/rust/springylib/src/media/mod.rs +++ b/rust/springylib/src/media/mod.rs @@ -1 +1,6 @@ +pub mod font; +pub mod level; +pub mod rle; +pub mod sprites; +pub mod txt; pub mod ui; diff --git a/rust/springylib/src/media/rle/gif.rs b/rust/springylib/src/media/rle/gif.rs new file mode 100644 index 0000000..9653db8 --- /dev/null +++ b/rust/springylib/src/media/rle/gif.rs @@ -0,0 +1,29 @@ +use crate::media::rle::{bgra_to_rgba, RleImage}; +use image::error::{LimitError, LimitErrorKind}; +use image::{AnimationDecoder, Delay, Frame, Frames, ImageBuffer, ImageError}; +use std::time::Duration; + +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(ImageError::Limits(LimitError::from_kind( + LimitErrorKind::InsufficientMemory, + )))?; + Ok(Frame::from_parts( + buffer, + frame.left, + frame.top, + Delay::from_saturating_duration(Duration::from_millis(80)), + )) + }))) + } +} diff --git a/rust/src/formats/rle.rs b/rust/springylib/src/media/rle/mod.rs similarity index 68% rename from rust/src/formats/rle.rs rename to rust/springylib/src/media/rle/mod.rs index 60f1f39..179d2df 100644 --- a/rust/src/formats/rle.rs +++ b/rust/springylib/src/media/rle/mod.rs @@ -1,9 +1,9 @@ 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; + +#[cfg(all(feature = "rle_gif"))] +pub mod gif; #[binread] #[br(little, magic = 0x67u32)] @@ -93,39 +93,6 @@ impl RleImage { } } -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(err: T) -> ImageError -where - T: Into>, -{ - ImageError::Decoding(DecodingError::new( - ImageFormatHint::Name(String::from("mhjnr_rle")), - err, - )) -} diff --git a/rust/src/formats/sprites.rs b/rust/springylib/src/media/sprites.rs similarity index 67% rename from rust/src/formats/sprites.rs rename to rust/springylib/src/media/sprites.rs index 9fb57a7..6f3adca 100644 --- a/rust/src/formats/sprites.rs +++ b/rust/springylib/src/media/sprites.rs @@ -1,3 +1,5 @@ +use crate::error::Error; + #[derive(Debug)] pub struct Sprites { pub name: String, @@ -7,12 +9,6 @@ pub struct Sprites { pub frames: Option, } -#[derive(Debug)] -pub enum Error { - InvalidData, - UnknownEnum(String), -} - impl Sprites { pub fn parse(string: &str) -> Result, Error> { string @@ -25,20 +21,25 @@ impl Sprites { pub fn parse_single(string: &str) -> Result { let mut components = string.split_whitespace(); + let invalid_data = |info| Error::InvalidData { + info, + context: string.to_string(), + }; + let eof = || invalid_data(Some("eof".to_string())); Ok(Sprites { - file_name: components.next().ok_or(Error::InvalidData)?.to_string(), - sprite_type: match components.next().ok_or(Error::InvalidData)? { + file_name: components.next().ok_or_else(eof)?.to_string(), + sprite_type: match components.next().ok_or_else(eof)? { "anim_rle" => SpriteType::AnimRle, "anim" => SpriteType::Anim, "static" => SpriteType::Static, - e => return Err(Error::UnknownEnum(e.to_string())), + e => return Err(invalid_data(Some(e.to_string()))), }, - name: components.next().ok_or(Error::InvalidData)?.to_string(), - render_mode: match components.next().ok_or(Error::InvalidData)? { + name: components.next().ok_or_else(eof)?.to_string(), + render_mode: match components.next().ok_or_else(eof)? { "normx" => RenderMode::NormX, "flipx" => RenderMode::FlipX, - e => return Err(Error::UnknownEnum(e.to_string())), + e => return Err(invalid_data(Some(e.to_string()))), }, frames: if let Some(c) = components.next() { Some(match c { @@ -46,7 +47,7 @@ impl Sprites { x => x .parse::() .map(CropMode::FrameCount) - .map_err(|e| Error::UnknownEnum(e.to_string()))?, + .map_err(|err| Error::Custom(Box::new(err)))?, }) } else { None diff --git a/rust/src/formats/txt.rs b/rust/springylib/src/media/txt.rs similarity index 65% rename from rust/src/formats/txt.rs rename to rust/springylib/src/media/txt.rs index eee0bf5..8d9c8b2 100644 --- a/rust/src/formats/txt.rs +++ b/rust/springylib/src/media/txt.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; use std::num::ParseIntError; use std::string::FromUtf8Error; @@ -7,6 +8,17 @@ pub enum DecryptError { ParseIntError(ParseIntError), } +impl Display for DecryptError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + DecryptError::FromUtf8Error(error) => write!(f, "{}", error), + DecryptError::ParseIntError(error) => write!(f, "{}", error), + } + } +} + +impl std::error::Error for DecryptError {} + impl From for DecryptError { fn from(e: FromUtf8Error) -> DecryptError { DecryptError::FromUtf8Error(e) @@ -65,4 +77,25 @@ pub fn decrypt_exposed_txt(contents: String) -> Result { } #[cfg(test)] -mod tests {} +mod tests { + use crate::media::txt::{decrypt_exposed_txt, decrypt_txt, from_hex}; + + #[test] + fn it_should_parse_hex() { + assert_eq!(from_hex("abcdef").unwrap(), vec![0xab, 0xcd, 0xef]); + } + + #[test] + fn it_should_decrypt() { + let data: Vec = vec![0x3a, 0x9b, 0x6f, 0x09, 0x7e, 0xd3, 0x74, 0xd6]; + assert_eq!(decrypt_txt(data.into_iter()).unwrap(), "\r\nsound ",) + } + + #[test] + fn it_should_decrypt_exposed() { + assert_eq!( + decrypt_exposed_txt("83\r\n248ecc86d5d85f6fc6626a6ef5be3e".to_string()).unwrap(), + "{\r\n \"isValid\" 1" + ) + } +} diff --git a/rust/springylib/src/media/ui/mod.rs b/rust/springylib/src/media/ui/mod.rs index 23cedec..82c4424 100644 --- a/rust/springylib/src/media/ui/mod.rs +++ b/rust/springylib/src/media/ui/mod.rs @@ -54,8 +54,8 @@ impl Default for FadeMode { } impl UiTag { - pub fn post_process(&mut self) { - if let UiTag::Menu(menu) = self { + pub fn post_process(mut self) -> Self { + if let UiTag::Menu(mut menu) = &self { let children: Vec = menu.children.drain(..).collect(); let mut area_stack: Vec> = vec![vec![]]; @@ -84,6 +84,8 @@ impl UiTag { menu.children = area_stack.pop().unwrap(); debug_assert!(area_stack.is_empty()); } + + self } } @@ -102,8 +104,7 @@ mod tests { #[test] fn it_should_post_process() { - let mut xml: UiTag = serde_xml_rs::from_str(XML).unwrap(); - xml.post_process(); + let mut xml = serde_xml_rs::from_str::(XML).unwrap().post_process(); if let UiTag::Menu(UiMenu { children, .. }) = xml { if let &[UiTag::TextArea(UiTextArea { children, .. })] = &children.as_slice() { diff --git a/rust/src/formats/level.rs b/rust/src/formats/level.rs deleted file mode 100644 index 9612497..0000000 --- a/rust/src/formats/level.rs +++ /dev/null @@ -1,46 +0,0 @@ -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, -} - -pub fn level_tile_data_to_image(tile_data: &[u8]) -> ImageResult { - 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, - )) -}