From 0c095125de8840d09cc825dfc241b3e4d3617335 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 17 Sep 2023 02:45:17 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D1=8B=D1=85=20=D0=BD=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BE=D0=BA=20=D0=B2=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 771 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- LICENSE.txt | 339 +++++++++++++++++ README.md | 6 + libnres/Cargo.toml | 16 + libnres/README.md | 25 ++ libnres/src/converter.rs | 33 ++ libnres/src/error.rs | 45 +++ libnres/src/lib.rs | 24 ++ libnres/src/reader.rs | 227 ++++++++++++ nres-cli/Cargo.toml | 20 + nres-cli/README.md | 6 + nres-cli/src/main.rs | 198 ++++++++++ 13 files changed, 1711 insertions(+), 1 deletion(-) create mode 100644 LICENSE.txt create mode 100644 libnres/Cargo.toml create mode 100644 libnres/README.md create mode 100644 libnres/src/converter.rs create mode 100644 libnres/src/error.rs create mode 100644 libnres/src/lib.rs create mode 100644 libnres/src/reader.rs create mode 100644 nres-cli/Cargo.toml create mode 100644 nres-cli/README.md create mode 100644 nres-cli/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 5d240a1..85a1fef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,421 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "indicatif" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libnres" +version = "0.1.4" +dependencies = [ + "byteorder", + "log", + "miette", + "thiserror", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "backtrace", + "backtrace-ext", + "is-terminal", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "nres-cli" +version = "0.2.3" +dependencies = [ + "byteorder", + "clap", + "console", + "dialoguer", + "indicatif", + "libnres", + "miette", + "tempdir", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "packer" version = "0.1.0" @@ -23,6 +426,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "portable-atomic" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" + [[package]] name = "proc-macro2" version = "1.0.67" @@ -41,6 +450,80 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.15" @@ -78,6 +561,52 @@ dependencies = [ "serde", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "supports-color" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +dependencies = [ + "is-terminal", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "supports-unicode" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" +dependencies = [ + "is-terminal", +] + [[package]] name = "syn" version = "2.0.36" @@ -89,12 +618,88 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unpacker" version = "0.1.1" @@ -103,3 +708,169 @@ dependencies = [ "serde", "serde_json", ] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 3fc2b87..95c9ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["packer", "unpacker"] +members = ["libnres", "nres-cli", "packer", "unpacker"] [profile.release] codegen-units = 1 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md index e69de29..3537a5b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,6 @@ +# Utilities for the game "Parkan: Iron Strategy" + +## List of projects + +- [libnres](libnres): Library for NRes files. +- [nres-cli](nres-cli): Console tool for NRes files. diff --git a/libnres/Cargo.toml b/libnres/Cargo.toml new file mode 100644 index 0000000..85597bd --- /dev/null +++ b/libnres/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libnres" +version = "0.1.4" +description = "Library for NRes files" +authors = ["Valentin Popov "] +homepage = "https://git.popov.link/valentineus/fparkan" +repository = "https://git.popov.link/valentineus/fparkan.git" +license = "GPL-2.0" +edition = "2021" +keywords = ["gamedev", "library", "nres"] + +[dependencies] +byteorder = "1.4" +log = "0.4" +miette = "5.6" +thiserror = "1.0" diff --git a/libnres/README.md b/libnres/README.md new file mode 100644 index 0000000..065bd40 --- /dev/null +++ b/libnres/README.md @@ -0,0 +1,25 @@ +# Library for NRes files (Deprecated) + +Library for viewing and retrieving game resources of the game **"Parkan: Iron Strategy"**. +All versions of the game are supported: Demo, IS, IS: Part 1, IS: Part 2. +Supports files with `lib`, `trf`, `rlb` extensions. + +The files `gamefont.rlb` and `sprites.lib` are not supported. +This files have an unknown signature. + +## Example + +Example of extracting game resources: + +```rust +fn main() { + let file = std::fs::File::open("./voices.lib").unwrap(); + // Extracting the list of files + let list = libnres::reader::get_list(&file).unwrap(); + + for element in list { + // Extracting the contents of the file + let data = libnres::reader::get_file(&file, &element).unwrap(); + } +} +``` diff --git a/libnres/src/converter.rs b/libnres/src/converter.rs new file mode 100644 index 0000000..bbf0535 --- /dev/null +++ b/libnres/src/converter.rs @@ -0,0 +1,33 @@ +use crate::error::ConverterError; + +/// Method for converting u32 to u64. +pub fn u32_to_u64(value: u32) -> Result { + match u64::try_from(value) { + Err(error) => Err(ConverterError::Infallible(error)), + Ok(result) => Ok(result), + } +} + +/// Method for converting u32 to usize. +pub fn u32_to_usize(value: u32) -> Result { + match usize::try_from(value) { + Err(error) => Err(ConverterError::TryFromIntError(error)), + Ok(result) => Ok(result), + } +} + +/// Method for converting u64 to u32. +pub fn u64_to_u32(value: u64) -> Result { + match u32::try_from(value) { + Err(error) => Err(ConverterError::TryFromIntError(error)), + Ok(result) => Ok(result), + } +} + +/// Method for converting usize to u32. +pub fn usize_to_u32(value: usize) -> Result { + match u32::try_from(value) { + Err(error) => Err(ConverterError::TryFromIntError(error)), + Ok(result) => Ok(result), + } +} diff --git a/libnres/src/error.rs b/libnres/src/error.rs new file mode 100644 index 0000000..440ab06 --- /dev/null +++ b/libnres/src/error.rs @@ -0,0 +1,45 @@ +extern crate miette; +extern crate thiserror; + +use miette::Diagnostic; +use thiserror::Error; + +#[derive(Error, Diagnostic, Debug)] +pub enum ConverterError { + #[error("error converting an value")] + #[diagnostic(code(libnres::infallible))] + Infallible(#[from] std::convert::Infallible), + + #[error("error converting an value")] + #[diagnostic(code(libnres::try_from_int_error))] + TryFromIntError(#[from] std::num::TryFromIntError), +} + +#[derive(Error, Diagnostic, Debug)] +pub enum ReaderError { + #[error(transparent)] + #[diagnostic(code(libnres::convert_error))] + ConvertValue(#[from] ConverterError), + + #[error("incorrect header format")] + #[diagnostic(code(libnres::list_type_error))] + IncorrectHeader, + + #[error("incorrect file size (expected {expected:?} bytes, received {received:?} bytes)")] + #[diagnostic(code(libnres::file_size_error))] + IncorrectSizeFile { expected: u32, received: u32 }, + + #[error( + "incorrect size of the file list (not a multiple of {expected:?}, received {received:?})" + )] + #[diagnostic(code(libnres::list_size_error))] + IncorrectSizeList { expected: u32, received: u32 }, + + #[error("resource file reading error")] + #[diagnostic(code(libnres::io_error))] + ReadFile(#[from] std::io::Error), + + #[error("file is too small (must be at least {expected:?} bytes, received {received:?} byte)")] + #[diagnostic(code(libnres::file_size_error))] + SmallFile { expected: u32, received: u32 }, +} diff --git a/libnres/src/lib.rs b/libnres/src/lib.rs new file mode 100644 index 0000000..40c0b32 --- /dev/null +++ b/libnres/src/lib.rs @@ -0,0 +1,24 @@ +/// First constant value of the NRes file ("NRes" characters in numeric) +pub const FILE_TYPE_1: u32 = 1936020046; +/// Second constant value of the NRes file +pub const FILE_TYPE_2: u32 = 256; +/// Size of the element item (in bytes) +pub const LIST_ELEMENT_SIZE: u32 = 64; +/// Minimum allowed file size (in bytes) +pub const MINIMUM_FILE_SIZE: u32 = 16; + +static DEBUG: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +mod converter; +mod error; +pub mod reader; + +/// Get debug status value +pub fn get_debug() -> bool { + DEBUG.load(std::sync::atomic::Ordering::Relaxed) +} + +/// Change debug status value +pub fn set_debug(value: bool) { + DEBUG.store(value, std::sync::atomic::Ordering::Relaxed) +} diff --git a/libnres/src/reader.rs b/libnres/src/reader.rs new file mode 100644 index 0000000..2a450ee --- /dev/null +++ b/libnres/src/reader.rs @@ -0,0 +1,227 @@ +use std::io::{Read, Seek}; + +use byteorder::ByteOrder; + +use crate::error::ReaderError; +use crate::{converter, FILE_TYPE_1, FILE_TYPE_2, LIST_ELEMENT_SIZE, MINIMUM_FILE_SIZE}; + +#[derive(Debug)] +pub struct ListElement { + /// Unknown parameter + _unknown0: i32, + /// Unknown parameter + _unknown1: i32, + /// Unknown parameter + _unknown2: i32, + /// File extension + pub extension: String, + /// Identifier or sequence number + pub index: u32, + /// File name + pub name: String, + /// Position in the file + pub position: u32, + /// File size (in bytes) + pub size: u32, +} + +impl ListElement { + /// Get full name of the file + pub fn get_filename(&self) -> String { + format!("{}.{}", self.name, self.extension) + } +} + +#[derive(Debug)] +pub struct FileHeader { + /// File size + size: u32, + /// Number of files + total: u32, + /// First constant value + type1: u32, + /// Second constant value + type2: u32, +} + +/// Get a packed file data +pub fn get_file(file: &std::fs::File, element: &ListElement) -> Result, ReaderError> { + let size = get_file_size(file)?; + check_file_size(size)?; + + let header = get_file_header(file)?; + check_file_header(&header, size)?; + + let data = get_element_data(file, element)?; + Ok(data) +} + +/// Get a list of packed files +pub fn get_list(file: &std::fs::File) -> Result, ReaderError> { + let mut list: Vec = Vec::new(); + + let size = get_file_size(file)?; + check_file_size(size)?; + + let header = get_file_header(file)?; + check_file_header(&header, size)?; + + get_file_list(file, &header, &mut list)?; + + Ok(list) +} + +fn check_file_header(header: &FileHeader, size: u32) -> Result<(), ReaderError> { + if header.type1 != FILE_TYPE_1 || header.type2 != FILE_TYPE_2 { + return Err(ReaderError::IncorrectHeader); + } + + if header.size != size { + return Err(ReaderError::IncorrectSizeFile { + expected: size, + received: header.size, + }); + } + + Ok(()) +} + +fn check_file_size(size: u32) -> Result<(), ReaderError> { + if size < MINIMUM_FILE_SIZE { + return Err(ReaderError::SmallFile { + expected: MINIMUM_FILE_SIZE, + received: size, + }); + } + + Ok(()) +} + +fn get_element_data(file: &std::fs::File, element: &ListElement) -> Result, ReaderError> { + let position = converter::u32_to_u64(element.position)?; + let size = converter::u32_to_usize(element.size)?; + + let mut reader = std::io::BufReader::new(file); + let mut buffer = vec![0u8; size]; + + if let Err(error) = reader.seek(std::io::SeekFrom::Start(position)) { + return Err(ReaderError::ReadFile(error)); + }; + + if let Err(error) = reader.read_exact(&mut buffer) { + return Err(ReaderError::ReadFile(error)); + }; + + Ok(buffer) +} + +fn get_element_position(index: u32) -> Result<(usize, usize), ReaderError> { + let from = converter::u32_to_usize(index * LIST_ELEMENT_SIZE)?; + let to = converter::u32_to_usize((index * LIST_ELEMENT_SIZE) + LIST_ELEMENT_SIZE)?; + Ok((from, to)) +} + +fn get_file_header(file: &std::fs::File) -> Result { + let mut reader = std::io::BufReader::new(file); + let mut buffer = vec![0u8; MINIMUM_FILE_SIZE as usize]; + + if let Err(error) = reader.seek(std::io::SeekFrom::Start(0)) { + return Err(ReaderError::ReadFile(error)); + }; + + if let Err(error) = reader.read_exact(&mut buffer) { + return Err(ReaderError::ReadFile(error)); + }; + + let header = FileHeader { + size: byteorder::LittleEndian::read_u32(&buffer[12..16]), + total: byteorder::LittleEndian::read_u32(&buffer[8..12]), + type1: byteorder::LittleEndian::read_u32(&buffer[0..4]), + type2: byteorder::LittleEndian::read_u32(&buffer[4..8]), + }; + + buffer.clear(); + Ok(header) +} + +fn get_file_list( + file: &std::fs::File, + header: &FileHeader, + list: &mut Vec, +) -> Result<(), ReaderError> { + let (start_position, list_size) = get_list_position(header)?; + let mut reader = std::io::BufReader::new(file); + let mut buffer = vec![0u8; list_size]; + + if let Err(error) = reader.seek(std::io::SeekFrom::Start(start_position)) { + return Err(ReaderError::ReadFile(error)); + }; + + if let Err(error) = reader.read_exact(&mut buffer) { + return Err(ReaderError::ReadFile(error)); + } + + let buffer_size = converter::usize_to_u32(buffer.len())?; + + if buffer_size % LIST_ELEMENT_SIZE != 0 { + return Err(ReaderError::IncorrectSizeList { + expected: LIST_ELEMENT_SIZE, + received: buffer_size, + }); + } + + for i in 0..(buffer_size / LIST_ELEMENT_SIZE) { + let (from, to) = get_element_position(i)?; + let chunk: &[u8] = &buffer[from..to]; + + let element = get_list_element(chunk)?; + list.push(element); + } + + buffer.clear(); + Ok(()) +} + +fn get_file_size(file: &std::fs::File) -> Result { + let metadata = match file.metadata() { + Err(error) => return Err(ReaderError::ReadFile(error)), + Ok(value) => value, + }; + + let result = converter::u64_to_u32(metadata.len())?; + Ok(result) +} + +fn get_list_element(buffer: &[u8]) -> Result { + let index = byteorder::LittleEndian::read_u32(&buffer[60..64]); + let position = byteorder::LittleEndian::read_u32(&buffer[56..60]); + let size = byteorder::LittleEndian::read_u32(&buffer[12..16]); + let unknown0 = byteorder::LittleEndian::read_i32(&buffer[4..8]); + let unknown1 = byteorder::LittleEndian::read_i32(&buffer[8..12]); + let unknown2 = byteorder::LittleEndian::read_i32(&buffer[16..20]); + + let extension = String::from_utf8_lossy(&buffer[0..4]) + .trim_matches(char::from(0)) + .to_string(); + + let name = String::from_utf8_lossy(&buffer[20..56]) + .trim_matches(char::from(0)) + .to_string(); + + Ok(ListElement { + _unknown0: unknown0, + _unknown1: unknown1, + _unknown2: unknown2, + extension, + index, + name, + position, + size, + }) +} + +fn get_list_position(header: &FileHeader) -> Result<(u64, usize), ReaderError> { + let position = converter::u32_to_u64(header.size - (header.total * LIST_ELEMENT_SIZE))?; + let size = converter::u32_to_usize(header.total * LIST_ELEMENT_SIZE)?; + Ok((position, size)) +} diff --git a/nres-cli/Cargo.toml b/nres-cli/Cargo.toml new file mode 100644 index 0000000..e3b1932 --- /dev/null +++ b/nres-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nres-cli" +version = "0.2.3" +description = "Console tool for NRes files" +authors = ["Valentin Popov "] +homepage = "https://git.popov.link/valentineus/fparkan" +repository = "https://git.popov.link/valentineus/fparkan.git" +license = "GPL-2.0" +edition = "2021" +keywords = ["cli", "gamedev", "nres"] + +[dependencies] +byteorder = "1.4" +clap = { version = "4.2", features = ["derive"] } +console = "0.15" +dialoguer = { version = "0.10", features = ["completion"] } +indicatif = "0.17" +libnres = { version = "0.1", path = "../libnres" } +miette = { version = "5.6", features = ["fancy"] } +tempdir = "0.3" diff --git a/nres-cli/README.md b/nres-cli/README.md new file mode 100644 index 0000000..65a6602 --- /dev/null +++ b/nres-cli/README.md @@ -0,0 +1,6 @@ +# Console tool for NRes files (Deprecated) + +## Commands + +- `extract` - Extract game resources from a "NRes" file. +- `ls` - Get a list of files in a "NRes" file. diff --git a/nres-cli/src/main.rs b/nres-cli/src/main.rs new file mode 100644 index 0000000..85086cb --- /dev/null +++ b/nres-cli/src/main.rs @@ -0,0 +1,198 @@ +extern crate core; +extern crate libnres; + +use std::io::Write; + +use clap::{Parser, Subcommand}; +use miette::{IntoDiagnostic, Result}; + +#[derive(Parser, Debug)] +#[command(name = "NRes CLI")] +#[command(about, author, version, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Check if the "NRes" file can be extract + Check { + /// "NRes" file + file: String, + }, + /// Print debugging information on the "NRes" file + #[command(arg_required_else_help = true)] + Debug { + /// "NRes" file + file: String, + /// Filter results by file name + #[arg(long)] + name: Option, + }, + /// Extract files or a file from the "NRes" file + #[command(arg_required_else_help = true)] + Extract { + /// "NRes" file + file: String, + /// Overwrite files + #[arg(short, long, default_value_t = false, value_name = "TRUE|FALSE")] + force: bool, + /// Outbound directory + #[arg(short, long, value_name = "DIR")] + out: String, + }, + /// Print a list of files in the "NRes" file + #[command(arg_required_else_help = true)] + Ls { + /// "NRes" file + file: String, + }, +} + +pub fn main() -> Result<()> { + let stdout = console::Term::stdout(); + let cli = Cli::parse(); + + match cli.command { + Commands::Check { file } => command_check(stdout, file)?, + Commands::Debug { file, name } => command_debug(stdout, file, name)?, + Commands::Extract { file, force, out } => command_extract(stdout, file, out, force)?, + Commands::Ls { file } => command_ls(stdout, file)?, + } + + Ok(()) +} + +fn command_check(_stdout: console::Term, file: String) -> Result<()> { + let file = std::fs::File::open(file).into_diagnostic()?; + let list = libnres::reader::get_list(&file).into_diagnostic()?; + let tmp = tempdir::TempDir::new("nres").into_diagnostic()?; + let bar = indicatif::ProgressBar::new(list.len() as u64); + + bar.set_style(get_bar_style()?); + + for element in list { + bar.set_message(element.get_filename()); + + let path = tmp.path().join(element.get_filename()); + let mut output = std::fs::File::create(path).into_diagnostic()?; + let mut buffer = libnres::reader::get_file(&file, &element).into_diagnostic()?; + + output.write_all(&buffer).into_diagnostic()?; + buffer.clear(); + bar.inc(1); + } + + bar.finish(); + + Ok(()) +} + +fn command_debug(stdout: console::Term, file: String, name: Option) -> Result<()> { + let file = std::fs::File::open(file).into_diagnostic()?; + let mut list = libnres::reader::get_list(&file).into_diagnostic()?; + + let mut total_files_size: u32 = 0; + let mut total_files_gap: u32 = 0; + let mut total_files: u32 = 0; + + for (index, item) in list.iter().enumerate() { + total_files_size += item.size; + total_files += 1; + let mut gap = 0; + + if index > 1 { + let previous_item = &list[index - 1]; + gap = item.position - (previous_item.position + previous_item.size); + } + + total_files_gap += gap; + } + + if let Some(name) = name { + list.retain(|item| item.name.contains(&name)); + }; + + for (index, item) in list.iter().enumerate() { + let mut gap = 0; + + if index > 1 { + let previous_item = &list[index - 1]; + gap = item.position - (previous_item.position + previous_item.size); + } + + let text = format!("Index: {};\nGap: {};\nItem: {:#?};\n", index, gap, item); + stdout.write_line(&text).into_diagnostic()?; + } + + let text = format!( + "Total files: {};\nTotal files gap: {} (bytes);\nTotal files size: {} (bytes);", + total_files, total_files_gap, total_files_size + ); + + stdout.write_line(&text).into_diagnostic()?; + + Ok(()) +} + +fn command_extract(_stdout: console::Term, file: String, out: String, force: bool) -> Result<()> { + let file = std::fs::File::open(file).into_diagnostic()?; + let list = libnres::reader::get_list(&file).into_diagnostic()?; + let bar = indicatif::ProgressBar::new(list.len() as u64); + + bar.set_style(get_bar_style()?); + + for element in list { + bar.set_message(element.get_filename()); + + let path = format!("{}/{}", out, element.get_filename()); + + if !force && is_exist_file(&path) { + let message = format!("File \"{}\" exists. Overwrite it?", path); + + if !dialoguer::Confirm::new() + .with_prompt(message) + .interact() + .into_diagnostic()? + { + continue; + } + } + + let mut output = std::fs::File::create(path).into_diagnostic()?; + let mut buffer = libnres::reader::get_file(&file, &element).into_diagnostic()?; + + output.write_all(&buffer).into_diagnostic()?; + buffer.clear(); + bar.inc(1); + } + + bar.finish(); + + Ok(()) +} + +fn command_ls(stdout: console::Term, file: String) -> Result<()> { + let file = std::fs::File::open(file).into_diagnostic()?; + let list = libnres::reader::get_list(&file).into_diagnostic()?; + + for element in list { + stdout.write_line(&element.name).into_diagnostic()?; + } + + Ok(()) +} + +fn get_bar_style() -> Result { + Ok( + indicatif::ProgressStyle::with_template("[{bar:32}] {pos:>7}/{len:7} {msg}") + .into_diagnostic()? + .progress_chars("=>-"), + ) +} + +fn is_exist_file(path: &String) -> bool { + let metadata = std::path::Path::new(path); + metadata.exists() +}