aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock771
-rw-r--r--Cargo.toml2
-rw-r--r--LICENSE.txt339
-rw-r--r--README.md6
-rw-r--r--libnres/Cargo.toml16
-rw-r--r--libnres/README.md25
-rw-r--r--libnres/src/converter.rs33
-rw-r--r--libnres/src/error.rs45
-rw-r--r--libnres/src/lib.rs24
-rw-r--r--libnres/src/reader.rs227
-rw-r--r--nres-cli/Cargo.toml20
-rw-r--r--nres-cli/README.md6
-rw-r--r--nres-cli/src/main.rs198
13 files changed, 1711 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5d240a1..85a1fef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,18 +3,421 @@
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"
dependencies = [
@@ -24,6 +427,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -42,6 +451,80 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -79,6 +562,52 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -90,12 +619,88 @@ dependencies = [
]
[[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"
dependencies = [
@@ -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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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.
+
+ <signature of Ty Coon>, 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 <valentin@popov.link>"]
+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<u64, ConverterError> {
+ 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<usize, ConverterError> {
+ 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<u32, ConverterError> {
+ 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<u32, ConverterError> {
+ 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<Vec<u8>, 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<Vec<ListElement>, ReaderError> {
+ let mut list: Vec<ListElement> = 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<Vec<u8>, 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<FileHeader, ReaderError> {
+ 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<ListElement>,
+) -> 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<u32, ReaderError> {
+ 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<ListElement, ReaderError> {
+ 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 <valentin@popov.link>"]
+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<String>,
+ },
+ /// 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<String>) -> 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<indicatif::ProgressStyle> {
+ 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()
+}