Compare commits
21 Commits
87e862c601
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
3f91386763
|
|||
|
560562cc25
|
|||
|
f404f597c1
|
|||
|
b3126d1798
|
|||
|
bdd5d849eb
|
|||
|
ddf76602be
|
|||
|
4a152f07da
|
|||
|
afc3f68f36
|
|||
|
fcc65f68bb
|
|||
|
03a127f24b
|
|||
|
ed9e23ed3b
|
|||
|
3949723303
|
|||
|
281327d69c
|
|||
|
9e3bdeacc6
|
|||
|
aacc9795d9
|
|||
|
22c27a2491
|
|||
|
132f784d58
|
|||
|
9df1ec77ef
|
|||
|
e0fd8aa8ea
|
|||
|
0e72675965
|
|||
|
44bca33a17
|
608
Cargo.lock
generated
608
Cargo.lock
generated
@@ -8,6 +8,17 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -97,21 +108,6 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit-set"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
|
|
||||||
dependencies = [
|
|
||||||
"bit-vec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit-vec"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -127,6 +123,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
@@ -143,18 +148,30 @@ version = "3.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
|
||||||
|
dependencies = [
|
||||||
|
"libbz2-rs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cbc"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.56"
|
version = "1.2.56"
|
||||||
@@ -162,6 +179,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -172,12 +191,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "cipher"
|
||||||
version = "0.4.44"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -241,11 +261,17 @@ version = "0.1.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -284,21 +310,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc"
|
|
||||||
version = "3.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
|
|
||||||
dependencies = [
|
|
||||||
"crc-catalog",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc-catalog"
|
|
||||||
version = "2.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -349,6 +360,12 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate64"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -366,6 +383,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -474,28 +492,6 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filetime"
|
|
||||||
version = "0.2.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"libredox",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filetime_creation"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"filetime",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -516,6 +512,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -542,12 +539,14 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
"sevenz-rust",
|
"sevenz-rust2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml",
|
"toml",
|
||||||
|
"unrar",
|
||||||
"ureq",
|
"ureq",
|
||||||
"url",
|
"url",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -580,6 +579,33 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi 5.3.0",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"r-efi 6.0.0",
|
||||||
|
"wasip2",
|
||||||
|
"wasip3",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.18"
|
version = "0.4.18"
|
||||||
@@ -634,6 +660,15 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -731,6 +766,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -760,6 +801,18 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||||
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -798,6 +851,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.91"
|
version = "0.3.91"
|
||||||
@@ -817,6 +880,18 @@ dependencies = [
|
|||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libbz2-rs-sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.182"
|
version = "0.2.182"
|
||||||
@@ -866,10 +941,7 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
|
||||||
"libc",
|
"libc",
|
||||||
"plain",
|
|
||||||
"redox_syscall",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -904,12 +976,12 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-rust"
|
name = "lzma-rust2"
|
||||||
version = "0.1.7"
|
version = "0.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661"
|
checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -937,16 +1009,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nt-time"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -999,6 +1061,16 @@ dependencies = [
|
|||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbkdf2"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -1028,10 +1100,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plain"
|
name = "pkg-config"
|
||||||
version = "0.2.3"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
@@ -1063,6 +1135,22 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppmd-rust"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -1091,6 +1179,18 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -1111,22 +1211,13 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.7.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@@ -1178,7 +1269,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
"libc",
|
"libc",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -1274,6 +1365,12 @@ dependencies = [
|
|||||||
"hashlink",
|
"hashlink",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -1327,22 +1424,34 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sevenz-rust"
|
name = "sevenz-rust2"
|
||||||
version = "0.6.1"
|
version = "0.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef"
|
checksum = "29225600349ef74beda5a9fffb36ac660a24613c0bde9315d0c49be1d51e9c24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"aes",
|
||||||
"byteorder",
|
"bzip2",
|
||||||
"crc",
|
"cbc",
|
||||||
"filetime_creation",
|
"crc32fast",
|
||||||
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lzma-rust",
|
"lzma-rust2",
|
||||||
"nt-time",
|
"ppmd-rust",
|
||||||
"sha2",
|
"sha2",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
@@ -1440,6 +1549,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"js-sys",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -1521,6 +1631,12 @@ version = "1.0.6+spec-1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-path"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
@@ -1545,6 +1661,35 @@ version = "1.0.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unrar"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"regex",
|
||||||
|
"unrar_sys",
|
||||||
|
"widestring",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unrar_sys"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1635,6 +1780,24 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.2+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip3"
|
||||||
|
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.114"
|
version = "0.2.114"
|
||||||
@@ -1680,6 +1843,40 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -1689,6 +1886,28 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "widestring"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1698,6 +1917,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -1801,6 +2026,94 @@ version = "0.7.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -1856,6 +2169,20 @@ name = "zeroize"
|
|||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
@@ -1890,8 +2217,81 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "8.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"bzip2",
|
||||||
|
"constant_time_eq",
|
||||||
|
"crc32fast",
|
||||||
|
"deflate64",
|
||||||
|
"flate2",
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"hmac",
|
||||||
|
"indexmap",
|
||||||
|
"lzma-rust2",
|
||||||
|
"memchr",
|
||||||
|
"pbkdf2",
|
||||||
|
"ppmd-rust",
|
||||||
|
"sha1",
|
||||||
|
"time",
|
||||||
|
"typed-path",
|
||||||
|
"zeroize",
|
||||||
|
"zopfli",
|
||||||
|
"zstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zopfli"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"crc32fast",
|
||||||
|
"log",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "7.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.16+zstd.1.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ libloot = "0.29.0"
|
|||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
quick-xml = { version = "0.39.2", features = ["serde-types", "serialize"] }
|
quick-xml = { version = "0.39.2", features = ["serde-types", "serialize"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
sevenz-rust = { version = "0.6.1", default-features = false }
|
sevenz-rust2 = { version = "0.20.2" }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
toml = "1.0.3"
|
toml = "1.0.3"
|
||||||
|
unrar = "0.5.8"
|
||||||
ureq = { version = "3.2.0", features = ["json"] }
|
ureq = { version = "3.2.0", features = ["json"] }
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
|
zip = "8.2.0"
|
||||||
|
|||||||
11
src/actions.rs
Normal file
11
src/actions.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
mod activate;
|
||||||
|
mod download;
|
||||||
|
mod include;
|
||||||
|
mod install;
|
||||||
|
mod load_order;
|
||||||
|
|
||||||
|
pub use activate::{ActivationError, activate_instance};
|
||||||
|
pub use download::handle_nxm;
|
||||||
|
pub use include::insert_mod_to_instance;
|
||||||
|
pub use install::resolve_files_for_install;
|
||||||
|
pub use load_order::{LoadOrderError, create_loadorder};
|
||||||
@@ -7,6 +7,7 @@ use std::io::Write;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, io, os::unix, path::Path};
|
use std::{fs, io, os::unix, path::Path};
|
||||||
|
|
||||||
|
/// Create the symlinks for a instance in a given directory
|
||||||
pub fn activate_instance(
|
pub fn activate_instance(
|
||||||
root_config: &RootConfig,
|
root_config: &RootConfig,
|
||||||
instance: &ModdedInstance,
|
instance: &ModdedInstance,
|
||||||
@@ -18,7 +19,7 @@ pub fn activate_instance(
|
|||||||
|
|
||||||
check_target_valid(&target)?;
|
check_target_valid(&target)?;
|
||||||
|
|
||||||
let resolved_links = resolve_links(root_config, instance, game)?;
|
let resolved_links = resolve_links(root_config, instance, &game)?;
|
||||||
|
|
||||||
resolved_links
|
resolved_links
|
||||||
.iter()
|
.iter()
|
||||||
44
src/actions/download.rs
Normal file
44
src/actions/download.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
nexus::{NXMUrl, download_nxm},
|
||||||
|
types::{ModConfig, RootConfig},
|
||||||
|
unpacker::unpack,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handles a nexus mod url. Downloads, unpacks and adds the mod to the config
|
||||||
|
pub fn handle_nxm(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Result<()> {
|
||||||
|
let Some(dl_location) = root_config.download_location() else {
|
||||||
|
return Err(anyhow!("No download location set"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(api_key) = root_config.nexus_api_key() else {
|
||||||
|
return Err(anyhow!("No API key provided"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(nxm_url) = NXMUrl::parse_url(raw_url) else {
|
||||||
|
return Err(anyhow!("Failed to parse URL"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (dl_file, mod_info) = download_nxm(api_key, &nxm_url, dl_location)?;
|
||||||
|
|
||||||
|
let mod_id = format!("{}-{}", mod_info.generate_id(), nxm_url.file);
|
||||||
|
if root_config.game_by_id(&mod_id).is_some() {
|
||||||
|
error!(
|
||||||
|
"Generated mod id already exists. Pleas install downloaded mod manually. Downloaded at {}",
|
||||||
|
&dl_file.to_string_lossy()
|
||||||
|
);
|
||||||
|
return Err(anyhow!("Mod with generated id already exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let extract_to = root_config.mod_location().join(&mod_id);
|
||||||
|
unpack(dl_file, extract_to)?;
|
||||||
|
|
||||||
|
let file_id: u64 = nxm_url.file.parse()?;
|
||||||
|
let new_mod = ModConfig::from_mod_info(&mod_id, &mod_id, &mod_info, file_id);
|
||||||
|
|
||||||
|
root_config.add_mod(&new_mod);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
70
src/actions/include.rs
Normal file
70
src/actions/include.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use log::warn;
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
file_conflict_solver::ConflictSolver,
|
||||||
|
types::{InstalledMod, ModConfig, ModFile, ModdedInstance},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn insert_mod_to_instance(
|
||||||
|
instance: &mut ModdedInstance,
|
||||||
|
from_mod: &ModConfig,
|
||||||
|
files_to_add: &[ModFile],
|
||||||
|
priority: isize,
|
||||||
|
) -> Option<FileConflict> {
|
||||||
|
let mut solver = ConflictSolver::new();
|
||||||
|
|
||||||
|
let mut installed_files: Vec<(ModFile, &InstalledMod)> = Vec::new();
|
||||||
|
for installed_mod in instance.mods() {
|
||||||
|
for link in installed_mod.files() {
|
||||||
|
let recreated_mod_file = ModFile::new(link.src(), link.dst(), 0);
|
||||||
|
installed_files.push((recreated_mod_file, installed_mod));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (file, from_mod) in &installed_files {
|
||||||
|
if let Some(conflict) = solver.add_file(file, from_mod) {
|
||||||
|
warn!("File conflict on already added file: {:?}", conflict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_mod = InstalledMod::new(from_mod.id(), priority);
|
||||||
|
for file in files_to_add {
|
||||||
|
if let Some(conflict) = solver.add_file(file, &new_mod) {
|
||||||
|
return Some(FileConflict {
|
||||||
|
lhs_mod_id: conflict.lhs_mod.mod_id().to_owned(),
|
||||||
|
rhs_mod_id: conflict.rhs_mod.mod_id().to_owned(),
|
||||||
|
path: conflict.rhs_file.dst().to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_link_tree = solver.export_files();
|
||||||
|
|
||||||
|
let mut map: HashMap<String, InstalledMod> = HashMap::new();
|
||||||
|
|
||||||
|
for (file, from_mod) in new_link_tree {
|
||||||
|
match map.get_mut(from_mod.mod_id()) {
|
||||||
|
Some(existing) => {
|
||||||
|
existing.add_file(file);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut new_mod = InstalledMod::new(from_mod.mod_id(), from_mod.priority());
|
||||||
|
new_mod.add_file(file);
|
||||||
|
map.insert(new_mod.mod_id().to_owned(), new_mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, installed_mod) in map {
|
||||||
|
instance.update_or_create_mod(&installed_mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileConflict {
|
||||||
|
pub lhs_mod_id: String,
|
||||||
|
pub rhs_mod_id: String,
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
@@ -1,79 +1,19 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
|
||||||
io,
|
io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use log::{debug, trace, warn};
|
use log::{debug, trace};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
file_conflict_solver::ConflictSolver,
|
|
||||||
fomod, install_prompt,
|
fomod, install_prompt,
|
||||||
mod_config_installer::run_fomod_installer,
|
mod_config_installer::run_fomod_installer,
|
||||||
types::{InstalledMod, ModConfig, ModFile, ModdedInstance, RootConfig},
|
types::{ModConfig, ModFile, ModdedInstance, RootConfig},
|
||||||
utils::{resolve_case_insensitive, walk_all_files},
|
utils::{resolve_case_insensitive, walk_all_files},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn insert_mod_to_instance(
|
pub fn resolve_files_for_install(
|
||||||
instance: &mut ModdedInstance,
|
|
||||||
from_mod: &ModConfig,
|
|
||||||
files: &[ModFile],
|
|
||||||
priority: isize,
|
|
||||||
) -> Result<(), InststanceError> {
|
|
||||||
let mut solver = ConflictSolver::new();
|
|
||||||
|
|
||||||
let mut installed_files: Vec<(ModFile, &InstalledMod)> = Vec::new();
|
|
||||||
for installed_mod in instance.mods() {
|
|
||||||
for link in installed_mod.files() {
|
|
||||||
let recreated_mod_file = ModFile::new(link.src(), link.dst(), 0);
|
|
||||||
installed_files.push((recreated_mod_file, installed_mod));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (file, from_mod) in &installed_files {
|
|
||||||
if let Some(conflict) = solver.add_file(file, from_mod) {
|
|
||||||
warn!("File conflict on already added file: {:?}", conflict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_mod = InstalledMod::new(from_mod.id(), priority);
|
|
||||||
for file in files {
|
|
||||||
if let Some(conflict) = solver.add_file(file, &new_mod) {
|
|
||||||
return Err(InststanceError::FileConflict {
|
|
||||||
lhs_mod_id: conflict.lhs_mod.mod_id().to_owned(),
|
|
||||||
rhs_mod_id: conflict.rhs_mod.mod_id().to_owned(),
|
|
||||||
path: conflict.rhs_file.dst().to_owned(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_link_tree = solver.export_files();
|
|
||||||
|
|
||||||
let mut map: HashMap<String, InstalledMod> = HashMap::new();
|
|
||||||
|
|
||||||
for (file, from_mod) in new_link_tree {
|
|
||||||
match map.get_mut(from_mod.mod_id()) {
|
|
||||||
Some(existing) => {
|
|
||||||
existing.add_file(file);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut new_mod = InstalledMod::new(from_mod.mod_id(), from_mod.priority());
|
|
||||||
new_mod.add_file(file);
|
|
||||||
map.insert(new_mod.mod_id().to_owned(), new_mod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, installed_mod) in map {
|
|
||||||
instance.update_or_create_mod(&installed_mod);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn files_to_install_mod(
|
|
||||||
root_config: &RootConfig,
|
root_config: &RootConfig,
|
||||||
instance: &ModdedInstance,
|
instance: &ModdedInstance,
|
||||||
mod_to_install: &ModConfig,
|
mod_to_install: &ModConfig,
|
||||||
@@ -126,14 +66,11 @@ fn install_fomod(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
trace!("Current loded plugins: {:?}", active_plugins);
|
trace!("Current loded plugins: {:?}", active_plugins);
|
||||||
let files = run_fomod_installer(module_config, &active_plugins, install_prompt::prompt)
|
let files = run_fomod_installer(module_config, &active_plugins, install_prompt::prompt)?;
|
||||||
.map_err(|_| InststanceError::FomodRunInstaller)?;
|
|
||||||
|
|
||||||
let mod_files: Vec<_> = files
|
let mod_files: Vec<_> = files
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| {
|
.map(|f| ModFile::from_installer(f.clone(), &mod_root))
|
||||||
ModFile::from_installer(f.clone(), &mod_root).map_err(InststanceError::FomodFinalize)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -223,22 +160,7 @@ fn should_be_included(path: impl AsRef<Path>) -> bool {
|
|||||||
| "ilstrings"
|
| "ilstrings"
|
||||||
| "dlstrings"
|
| "dlstrings"
|
||||||
| "dll"
|
| "dll"
|
||||||
|
| "swf"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum InststanceError {
|
|
||||||
#[error("Two mods write the same file")]
|
|
||||||
FileConflict {
|
|
||||||
lhs_mod_id: String,
|
|
||||||
rhs_mod_id: String,
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Failed to run fomod installer")]
|
|
||||||
FomodRunInstaller,
|
|
||||||
|
|
||||||
#[error("Failed to handle results of fomod installer")]
|
|
||||||
FomodFinalize(io::Error),
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,10 @@ use std::{
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::{types::{self, ModdedInstance, RootConfig}, utils::is_plugin_file};
|
use crate::{
|
||||||
|
types::{self, ModdedInstance, RootConfig},
|
||||||
|
utils::is_plugin_file,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn create_loadorder(
|
pub fn create_loadorder(
|
||||||
root_config: &RootConfig,
|
root_config: &RootConfig,
|
||||||
@@ -15,9 +15,8 @@ pub struct Args {
|
|||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
Activate { instance: String, target: PathBuf },
|
Activate { instance: String, target: PathBuf },
|
||||||
Add { instance: String, mod_id: String },
|
Include { instance: String, mod_id: String },
|
||||||
LoadOrder { instance: String },
|
LoadOrder { instance: String },
|
||||||
ApiCheck,
|
ApiCheck,
|
||||||
Download { url: String },
|
Download { url: String },
|
||||||
Unpack { id: String, path: String },
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ pub enum PluginTypeDescriptorEnum {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct DependencyPluginType {
|
pub struct DependencyPluginType {
|
||||||
|
#[serde(rename = "defaultType")]
|
||||||
pub default_type: PluginType,
|
pub default_type: PluginType,
|
||||||
pub patterns: DependencyPatternList,
|
pub patterns: DependencyPatternList,
|
||||||
}
|
}
|
||||||
@@ -129,7 +130,7 @@ pub struct DependencyPatternList {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct DependencyPattern {
|
pub struct DependencyPattern {
|
||||||
pub dependencies: CompositeDependency,
|
pub dependencies: Vec<CompositeDependency>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub typ: PluginType,
|
pub typ: PluginType,
|
||||||
}
|
}
|
||||||
@@ -347,7 +348,7 @@ pub struct ConditionalInstallPatternList {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ConditionalInstallPattern {
|
pub struct ConditionalInstallPattern {
|
||||||
pub dependencies: CompositeDependency,
|
pub dependencies: ModuleDependency,
|
||||||
pub files: FileList,
|
pub files: FileList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
pub mod activator;
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod file_conflict_solver;
|
pub mod file_conflict_solver;
|
||||||
pub mod fomod;
|
pub mod fomod;
|
||||||
pub mod install_prompt;
|
pub mod install_prompt;
|
||||||
pub mod instance;
|
|
||||||
pub mod load_order;
|
|
||||||
pub mod mod_config_installer;
|
pub mod mod_config_installer;
|
||||||
pub mod nexus;
|
pub mod nexus;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod unpacker;
|
pub mod unpacker;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod actions;
|
||||||
|
|||||||
94
src/main.rs
94
src/main.rs
@@ -4,13 +4,13 @@ use log::{debug, error, info};
|
|||||||
use std::{error::Error, path::Path};
|
use std::{error::Error, path::Path};
|
||||||
|
|
||||||
use fomod_manager::{
|
use fomod_manager::{
|
||||||
activator::activate_instance,
|
actions::{
|
||||||
|
activate_instance, create_loadorder, handle_nxm, insert_mod_to_instance,
|
||||||
|
resolve_files_for_install,
|
||||||
|
},
|
||||||
cli::{self, Args},
|
cli::{self, Args},
|
||||||
instance::{self, files_to_install_mod, insert_mod_to_instance},
|
nexus::NexusAPI,
|
||||||
load_order,
|
|
||||||
nexus::{NexusAPI, download_nxm},
|
|
||||||
types::RootConfig,
|
types::RootConfig,
|
||||||
unpacker::unpack,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn command_activate(
|
fn command_activate(
|
||||||
@@ -29,40 +29,23 @@ fn command_add(root_config: &RootConfig, instance_id: &str, mod_id: &str) -> any
|
|||||||
.mod_by_id(mod_id)
|
.mod_by_id(mod_id)
|
||||||
.ok_or(anyhow!("Can't find mod in config"))?;
|
.ok_or(anyhow!("Can't find mod in config"))?;
|
||||||
|
|
||||||
let files = files_to_install_mod(root_config, &instance, &mod_to_install)?;
|
let files = resolve_files_for_install(root_config, &instance, &mod_to_install)?;
|
||||||
|
|
||||||
match insert_mod_to_instance(&mut instance, &mod_to_install, &files, 0) {
|
match insert_mod_to_instance(&mut instance, &mod_to_install, &files, 0) {
|
||||||
Ok(_) => {
|
None => {
|
||||||
instance.save_to_file()?;
|
instance.save_to_file()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Some(conflict) => {
|
||||||
match &err {
|
|
||||||
instance::InststanceError::FileConflict {
|
|
||||||
lhs_mod_id,
|
|
||||||
rhs_mod_id,
|
|
||||||
path,
|
|
||||||
} => {
|
|
||||||
error!(
|
error!(
|
||||||
"File conflict between {} and {} at {}",
|
"File conflict between {} and {} at {}",
|
||||||
lhs_mod_id,
|
conflict.lhs_mod_id,
|
||||||
rhs_mod_id,
|
conflict.rhs_mod_id,
|
||||||
path.to_string_lossy()
|
conflict.path.to_string_lossy()
|
||||||
);
|
);
|
||||||
info!("To resolve file conflicts give one mod a higher priority in the config");
|
info!("To resolve file conflicts give one mod a higher priority in the config");
|
||||||
}
|
|
||||||
instance::InststanceError::FomodRunInstaller => {
|
|
||||||
error!("Failed to run FOMod installer");
|
|
||||||
}
|
|
||||||
instance::InststanceError::FomodFinalize(error) => {
|
|
||||||
error!(
|
|
||||||
"FOMod installer finished but failed to finalize result: {}",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Err(err.into())
|
Err(anyhow!("File conflict"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,57 +54,15 @@ fn command_order(root_config: &RootConfig, instance_id: &str) -> anyhow::Result<
|
|||||||
let mut instance = root_config.load_instance_by_id(instance_id)?;
|
let mut instance = root_config.load_instance_by_id(instance_id)?;
|
||||||
let game = root_config.game_by_id(instance.game_id()).unwrap();
|
let game = root_config.game_by_id(instance.game_id()).unwrap();
|
||||||
|
|
||||||
let new_load_order = load_order::create_loadorder(root_config, game, &instance)?;
|
let new_load_order = create_loadorder(root_config, &game, &instance)?;
|
||||||
|
|
||||||
instance.set_load_order(new_load_order);
|
instance.set_load_order(new_load_order);
|
||||||
|
|
||||||
instance.save_to_file()?;
|
instance.save_to_file()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_download(root_config: &mut RootConfig, nxm_url: &str) -> anyhow::Result<()> {
|
fn command_download(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Result<()> {
|
||||||
let Some(dl_location) = root_config.download_location() else {
|
handle_nxm(root_config, raw_url)?;
|
||||||
return Err(anyhow!("No download location set"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(api_key) = root_config.nexus_api_key() else {
|
|
||||||
return Err(anyhow!("No API key provided"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (dl_file, mod_info) = download_nxm(api_key, nxm_url, dl_location)?;
|
|
||||||
|
|
||||||
let mod_id = mod_info.generate_id();
|
|
||||||
if root_config.game_by_id(&mod_id).is_some() {
|
|
||||||
error!(
|
|
||||||
"Generated mod id already exists. Pleas install downloaded mod manually. Downloaded at {}",
|
|
||||||
&dl_file.to_string_lossy()
|
|
||||||
);
|
|
||||||
return Err(anyhow!("Mod with generated already exists"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_mod = unpack(root_config, &mod_id, dl_file)?;
|
|
||||||
|
|
||||||
root_config.add_mod(&new_mod);
|
|
||||||
|
|
||||||
root_config.save_to_file()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_unpack(
|
|
||||||
root_config: &mut RootConfig,
|
|
||||||
id: &str,
|
|
||||||
file: impl AsRef<Path>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if root_config.game_by_id(id).is_some() {
|
|
||||||
error!("Mod already present");
|
|
||||||
return Err(anyhow!("Mod already exists"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_mod = unpack(root_config, id, file)?;
|
|
||||||
|
|
||||||
root_config.add_mod(&new_mod);
|
|
||||||
|
|
||||||
root_config.save_to_file()?;
|
root_config.save_to_file()?;
|
||||||
|
|
||||||
@@ -148,7 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
cli::Commands::Activate { instance, target } => {
|
cli::Commands::Activate { instance, target } => {
|
||||||
command_activate(&root_config, &instance, &target)?;
|
command_activate(&root_config, &instance, &target)?;
|
||||||
}
|
}
|
||||||
cli::Commands::Add { instance, mod_id } => {
|
cli::Commands::Include { instance, mod_id } => {
|
||||||
command_add(&root_config, &instance, &mod_id)?;
|
command_add(&root_config, &instance, &mod_id)?;
|
||||||
}
|
}
|
||||||
cli::Commands::LoadOrder { instance } => {
|
cli::Commands::LoadOrder { instance } => {
|
||||||
@@ -161,9 +102,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
cli::Commands::Download { url } => {
|
cli::Commands::Download { url } => {
|
||||||
command_download(&mut root_config, &url)?;
|
command_download(&mut root_config, &url)?;
|
||||||
}
|
}
|
||||||
cli::Commands::Unpack { id, path } => {
|
|
||||||
command_unpack(&mut root_config, &id, path)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -176,7 +176,11 @@ fn resolve_plugin_type(
|
|||||||
PluginTypeDescriptorEnum::PluginType(plugin_type) => plugin_type.name,
|
PluginTypeDescriptorEnum::PluginType(plugin_type) => plugin_type.name,
|
||||||
PluginTypeDescriptorEnum::DependencyType(dependency_plugin_type) => {
|
PluginTypeDescriptorEnum::DependencyType(dependency_plugin_type) => {
|
||||||
for dep in &dependency_plugin_type.patterns.pattern {
|
for dep in &dependency_plugin_type.patterns.pattern {
|
||||||
if evaluate_dependency(&dep.dependencies, state, installed_plugins) {
|
if dep
|
||||||
|
.dependencies
|
||||||
|
.iter()
|
||||||
|
.all(|e| evaluate_dependency(e, state, installed_plugins))
|
||||||
|
{
|
||||||
return dep.typ.name;
|
return dep.typ.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,7 +246,7 @@ pub fn run_fomod_installer(
|
|||||||
// Evaluate conditional file installs based on final flag state
|
// Evaluate conditional file installs based on final flag state
|
||||||
if let Some(conditional) = &fomod_config.conditional_file_installs {
|
if let Some(conditional) = &fomod_config.conditional_file_installs {
|
||||||
for pattern in &conditional.patterns.pattern {
|
for pattern in &conditional.patterns.pattern {
|
||||||
if evaluate_dependency(&pattern.dependencies, &state, installed_plugins) {
|
if evaluate_module_depbendecy(&pattern.dependencies, &state, installed_plugins) {
|
||||||
state.add_files(&pattern.files);
|
state.add_files(&pattern.files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ mod api;
|
|||||||
mod downloader;
|
mod downloader;
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
pub use api::NexusAPI;
|
pub use api::{ModInfo, NexusAPI};
|
||||||
pub use downloader::download_nxm;
|
pub use downloader::download_nxm;
|
||||||
pub use url::NXMUrl;
|
pub use url::NXMUrl;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::nexus::NXMUrl;
|
use crate::{nexus::NXMUrl, types::GameType};
|
||||||
|
|
||||||
const NEXUS_ENDPOINT: &str = "https://api.nexusmods.com";
|
const NEXUS_ENDPOINT: &str = "https://api.nexusmods.com";
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ pub struct ModInfo {
|
|||||||
pub mod_id: u64,
|
pub mod_id: u64,
|
||||||
// pub game_id: u64,
|
// pub game_id: u64,
|
||||||
// pub allow_rating: bool,
|
// pub allow_rating: bool,
|
||||||
// pub domain_name: String,
|
pub domain_name: String,
|
||||||
// pub category_id: u64,
|
// pub category_id: u64,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
// pub endorsement_count: u64,
|
// pub endorsement_count: u64,
|
||||||
@@ -145,6 +145,10 @@ impl ModInfo {
|
|||||||
if short_name.len() > MAX_CHARS {
|
if short_name.len() > MAX_CHARS {
|
||||||
short_name.truncate(MAX_CHARS);
|
short_name.truncate(MAX_CHARS);
|
||||||
}
|
}
|
||||||
format!("{}-{}-{}", short_name, self.mod_id, self.version)
|
format!("{}-{}", short_name.to_lowercase(), self.mod_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_game_type(&self) -> GameType {
|
||||||
|
GameType::from_nexus_domain(&self.domain_name).unwrap_or(GameType::Unknown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,21 +14,18 @@ use crate::nexus::{
|
|||||||
|
|
||||||
pub fn download_nxm(
|
pub fn download_nxm(
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
link: &str,
|
nxm_url: &NXMUrl,
|
||||||
target_dir: impl AsRef<Path>,
|
target_dir: impl AsRef<Path>,
|
||||||
) -> anyhow::Result<(PathBuf, ModInfo)> {
|
) -> anyhow::Result<(PathBuf, ModInfo)> {
|
||||||
let api = NexusAPI::new(api_key);
|
let api = NexusAPI::new(api_key);
|
||||||
let Some(parsed_url) = NXMUrl::parse_url(link) else {
|
|
||||||
return Err(anyhow!("Failed to parse url"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mod_info = api.mod_info(&parsed_url.game, &parsed_url.mod_id)?;
|
let mod_info = api.mod_info(&nxm_url.game, &nxm_url.mod_id)?;
|
||||||
let links = api.generate_download_link_for_file(&parsed_url)?;
|
let links = api.generate_download_link_for_file(nxm_url)?;
|
||||||
let selected_mirror = links.first().unwrap();
|
let selected_mirror = links.first().unwrap();
|
||||||
let url = selected_mirror.parse_url()?;
|
let url = selected_mirror.parse_url()?;
|
||||||
let original_filename = url.path_segments().and_then(|mut e| e.next_back()).unwrap();
|
let original_filename = url.path_segments().and_then(|mut e| e.next_back()).unwrap();
|
||||||
let filename = gen_filename_for_mod(&mod_info, original_filename);
|
let filename = gen_filename_for_mod(&mod_info, &nxm_url.file, original_filename);
|
||||||
let download_path = target_dir.as_ref().join(parsed_url.game).join(filename);
|
let download_path = target_dir.as_ref().join(&nxm_url.game).join(filename);
|
||||||
|
|
||||||
if let Some(parent) = download_path.parent() {
|
if let Some(parent) = download_path.parent() {
|
||||||
create_dir_all(parent)?;
|
create_dir_all(parent)?;
|
||||||
@@ -58,12 +55,12 @@ fn download_mod(mod_dl_link: &DownloadLocation, target: impl AsRef<Path>) -> any
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_filename_for_mod(mod_info: &ModInfo, dl_filename: &str) -> String {
|
fn gen_filename_for_mod(mod_info: &ModInfo, file_id: &str, dl_filename: &str) -> String {
|
||||||
let filename_from_url = PathBuf::from(dl_filename);
|
let filename_from_url = PathBuf::from(dl_filename);
|
||||||
let ext = filename_from_url
|
let ext = filename_from_url
|
||||||
.extension()
|
.extension()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string_lossy();
|
.to_string_lossy();
|
||||||
|
|
||||||
format!("{}-{}.{}", mod_info.mod_id, mod_info.version, ext)
|
format!("{}-{}.{}", mod_info.mod_id, file_id, ext)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
|
mod game_type;
|
||||||
mod installed_mod;
|
mod installed_mod;
|
||||||
mod link;
|
mod link;
|
||||||
mod mod_config;
|
mod mod_config;
|
||||||
mod mod_file;
|
mod mod_file;
|
||||||
mod modded_instance;
|
mod modded_instance;
|
||||||
mod root_config;
|
mod root_config;
|
||||||
|
mod nexus_id;
|
||||||
|
|
||||||
pub use game::*;
|
pub use game::*;
|
||||||
|
pub use game_type::GameType;
|
||||||
pub use installed_mod::*;
|
pub use installed_mod::*;
|
||||||
pub use link::*;
|
pub use link::*;
|
||||||
pub use mod_config::*;
|
pub use mod_config::*;
|
||||||
pub use mod_file::*;
|
pub use mod_file::*;
|
||||||
pub use modded_instance::*;
|
pub use modded_instance::*;
|
||||||
pub use root_config::*;
|
pub use root_config::*;
|
||||||
|
pub use nexus_id::*;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ConfigReadWriteError {
|
pub enum ConfigReadWriteError {
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
io,
|
io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{types::link::Link, utils::walk_all_files};
|
use crate::{
|
||||||
|
types::{GameType, link::Link},
|
||||||
|
utils::walk_all_files,
|
||||||
|
};
|
||||||
|
|
||||||
/// Available game
|
/// Available game
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
kind: GameType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new(path: impl AsRef<Path>) -> Self {
|
pub fn new(path: impl AsRef<Path>, game_type: GameType) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: path.as_ref().to_owned(),
|
path: path.as_ref().to_owned(),
|
||||||
|
kind: game_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export_links(&self) -> Result<Vec<Link>, io::Error> {
|
pub fn export_links(&self) -> Result<HashSet<Link>, io::Error> {
|
||||||
let links: Vec<Link> = walk_all_files(&self.path)?
|
let links: HashSet<Link> = walk_all_files(&self.path)?
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
Link::new(
|
Link::new(
|
||||||
entry.path(),
|
entry.path(),
|
||||||
@@ -39,4 +45,8 @@ impl Game {
|
|||||||
pub fn install_location(&self) -> &Path {
|
pub fn install_location(&self) -> &Path {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn game_type(&self) -> GameType {
|
||||||
|
self.kind.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
186
src/types/game_type.rs
Normal file
186
src/types/game_type.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub enum GameType {
|
||||||
|
Oblivion,
|
||||||
|
Skyrim,
|
||||||
|
Fallout3,
|
||||||
|
FalloutNV,
|
||||||
|
Fallout4,
|
||||||
|
SkyrimSE,
|
||||||
|
Fallout4VR,
|
||||||
|
SkyrimVR,
|
||||||
|
Morrowind,
|
||||||
|
Starfield,
|
||||||
|
OpenMW,
|
||||||
|
OblivionRemastered,
|
||||||
|
Custom(String),
|
||||||
|
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameType {
|
||||||
|
pub fn to_libloot_type(self) -> Option<libloot::GameType> {
|
||||||
|
match self {
|
||||||
|
GameType::Oblivion => Some(libloot::GameType::Oblivion),
|
||||||
|
GameType::Skyrim => Some(libloot::GameType::Skyrim),
|
||||||
|
GameType::Fallout3 => Some(libloot::GameType::Fallout3),
|
||||||
|
GameType::FalloutNV => Some(libloot::GameType::FalloutNV),
|
||||||
|
GameType::Fallout4 => Some(libloot::GameType::Fallout4),
|
||||||
|
GameType::SkyrimSE => Some(libloot::GameType::SkyrimSE),
|
||||||
|
GameType::Fallout4VR => Some(libloot::GameType::Fallout4VR),
|
||||||
|
GameType::SkyrimVR => Some(libloot::GameType::SkyrimVR),
|
||||||
|
GameType::Morrowind => Some(libloot::GameType::Morrowind),
|
||||||
|
GameType::Starfield => Some(libloot::GameType::Starfield),
|
||||||
|
GameType::OpenMW => Some(libloot::GameType::OpenMW),
|
||||||
|
GameType::OblivionRemastered => Some(libloot::GameType::OblivionRemastered),
|
||||||
|
GameType::Custom(_) => None,
|
||||||
|
GameType::Unknown => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_nexus_domain(self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
GameType::Oblivion => Some("oblivion".to_owned()),
|
||||||
|
GameType::Skyrim => Some("skyrim".to_owned()),
|
||||||
|
GameType::Fallout3 => Some("fallout3".to_owned()),
|
||||||
|
GameType::FalloutNV => Some("newvegas".to_owned()),
|
||||||
|
GameType::Fallout4 => Some("fallout4".to_owned()),
|
||||||
|
GameType::SkyrimSE => Some("skyrimspecialedition".to_owned()),
|
||||||
|
GameType::Fallout4VR => Some("fallout4".to_owned()),
|
||||||
|
GameType::SkyrimVR => Some("skyrimspecialedition".to_owned()),
|
||||||
|
GameType::Morrowind => Some("morrowind".to_owned()),
|
||||||
|
GameType::Starfield => Some("starfield".to_owned()),
|
||||||
|
GameType::OpenMW => Some("morrowind".to_owned()),
|
||||||
|
GameType::OblivionRemastered => Some("oblivionremastered".to_owned()),
|
||||||
|
GameType::Custom(_) => None,
|
||||||
|
GameType::Unknown => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_nexus_domain(domain: &str) -> Option<Self> {
|
||||||
|
match domain {
|
||||||
|
"oblivion" => Some(GameType::Oblivion),
|
||||||
|
"skyrim" => Some(GameType::Skyrim),
|
||||||
|
"fallout3" => Some(GameType::Fallout3),
|
||||||
|
"newvegas" => Some(GameType::FalloutNV),
|
||||||
|
"fallout4" => Some(GameType::Fallout4),
|
||||||
|
"skyrimspecialedition" => Some(GameType::SkyrimSE),
|
||||||
|
"morrowind" => Some(GameType::Morrowind),
|
||||||
|
"starfield" => Some(GameType::Starfield),
|
||||||
|
"oblivionremastered" => Some(GameType::OblivionRemastered),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GameType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
GameType::Oblivion => "Oblivion",
|
||||||
|
GameType::Skyrim => "Skyrim",
|
||||||
|
GameType::Fallout3 => "Fallout 3",
|
||||||
|
GameType::FalloutNV => "Fallout New Vegas",
|
||||||
|
GameType::Fallout4 => "Fallout 4",
|
||||||
|
GameType::SkyrimSE => "Skyrim Special Edition",
|
||||||
|
GameType::Fallout4VR => "Fallout 4 VR",
|
||||||
|
GameType::SkyrimVR => "Skyrim VR",
|
||||||
|
GameType::Morrowind => "Morrowind",
|
||||||
|
GameType::Starfield => "Starfield",
|
||||||
|
GameType::OpenMW => "OpenMW",
|
||||||
|
GameType::OblivionRemastered => "Oblivion Remastered",
|
||||||
|
GameType::Custom(name) => name,
|
||||||
|
GameType::Unknown => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for GameType {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(match s.as_str() {
|
||||||
|
"oblivion" => Self::Oblivion,
|
||||||
|
"skyrim" => Self::Skyrim,
|
||||||
|
"fo3" => Self::Fallout3,
|
||||||
|
"fonv" => Self::FalloutNV,
|
||||||
|
"fo4" => Self::Fallout4,
|
||||||
|
"sse" => Self::SkyrimSE,
|
||||||
|
"fo4vr" => Self::Fallout4VR,
|
||||||
|
"skyrimvr" => Self::SkyrimVR,
|
||||||
|
"morrowind" => Self::Morrowind,
|
||||||
|
"starfield" => Self::Starfield,
|
||||||
|
"openmw" => Self::OpenMW,
|
||||||
|
"oblivionrm" => Self::OblivionRemastered,
|
||||||
|
"unknown" => Self::Unknown,
|
||||||
|
_ => Self::Custom(s),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for GameType {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let s = match self {
|
||||||
|
Self::Custom(s) => s,
|
||||||
|
Self::Oblivion => "oblivion",
|
||||||
|
Self::Skyrim => "skyrim",
|
||||||
|
Self::Fallout3 => "fo3",
|
||||||
|
Self::FalloutNV => "fonv",
|
||||||
|
Self::Fallout4 => "fo4",
|
||||||
|
Self::SkyrimSE => "sse",
|
||||||
|
Self::Fallout4VR => "fo4vr",
|
||||||
|
Self::SkyrimVR => "skyrimvr",
|
||||||
|
Self::Morrowind => "morrowind",
|
||||||
|
Self::Starfield => "starfield",
|
||||||
|
Self::OpenMW => "openmw",
|
||||||
|
Self::OblivionRemastered => "oblivionrm",
|
||||||
|
Self::Unknown => "unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
serializer.serialize_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||||
|
struct Wrapper {
|
||||||
|
value: GameType,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roundtrip(game_type: GameType) {
|
||||||
|
let val = Wrapper { value: game_type };
|
||||||
|
let serialized = toml::to_string(&val).unwrap();
|
||||||
|
let deserialized: Wrapper = toml::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(val, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_back_and_forth_all() {
|
||||||
|
for e in [
|
||||||
|
GameType::Oblivion,
|
||||||
|
GameType::Skyrim,
|
||||||
|
GameType::Fallout3,
|
||||||
|
GameType::FalloutNV,
|
||||||
|
GameType::Fallout4,
|
||||||
|
GameType::SkyrimSE,
|
||||||
|
GameType::Fallout4VR,
|
||||||
|
GameType::SkyrimVR,
|
||||||
|
GameType::Morrowind,
|
||||||
|
GameType::Starfield,
|
||||||
|
GameType::OpenMW,
|
||||||
|
GameType::OblivionRemastered,
|
||||||
|
GameType::Custom("custom".to_owned()),
|
||||||
|
GameType::Unknown,
|
||||||
|
] {
|
||||||
|
roundtrip(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::{self, Visitor},
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::{self, Debug},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::types::mod_file::ModFile;
|
use crate::types::mod_file::ModFile;
|
||||||
|
|
||||||
/// A link between a file from a mod and a destination in a ModdedInstance
|
/// A link between a file from a mod and a destination in a ModdedInstance
|
||||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
#[serde(from = "(PathBuf, PathBuf)", into = "(PathBuf,PathBuf)")]
|
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
dst: PathBuf,
|
dst: PathBuf,
|
||||||
@@ -36,18 +37,46 @@ impl Link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(PathBuf, PathBuf)> for Link {
|
impl Serialize for Link {
|
||||||
fn from(value: (PathBuf, PathBuf)) -> Self {
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
Self {
|
if self.src == self.dst {
|
||||||
src: value.0,
|
serializer.serialize_str(&self.src.to_string_lossy())
|
||||||
dst: value.1,
|
} else {
|
||||||
|
serializer.serialize_str(&format!(
|
||||||
|
"{} -> {}",
|
||||||
|
self.src.to_string_lossy(),
|
||||||
|
self.dst.to_string_lossy()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Link> for (PathBuf, PathBuf) {
|
impl<'de> Deserialize<'de> for Link {
|
||||||
fn from(value: Link) -> Self {
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
(value.src, value.dst)
|
struct LinkVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for LinkVisitor {
|
||||||
|
type Value = Link;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str(r#"a string like "src -> dst" or "path" if they are the same"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<Link, E> {
|
||||||
|
match value.split_once(" -> ") {
|
||||||
|
Some((src, dst)) => Ok(Link {
|
||||||
|
src: PathBuf::from(src),
|
||||||
|
dst: PathBuf::from(dst),
|
||||||
|
}),
|
||||||
|
None => Ok(Link {
|
||||||
|
src: PathBuf::from(value),
|
||||||
|
dst: PathBuf::from(value),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(LinkVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
nexus::ModInfo,
|
||||||
|
types::{GameType, NexusID},
|
||||||
|
};
|
||||||
|
|
||||||
/// Config for an available mod
|
/// Config for an available mod
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
@@ -22,6 +27,14 @@ pub struct ModConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
ignore: Vec<String>,
|
ignore: Vec<String>,
|
||||||
|
|
||||||
|
name: Option<String>,
|
||||||
|
|
||||||
|
nexus_id: Option<NexusID>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(skip_serializing_if = "is_default")]
|
||||||
|
game: GameType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModConfig {
|
impl ModConfig {
|
||||||
@@ -31,9 +44,26 @@ impl ModConfig {
|
|||||||
path: source.as_ref().to_owned(),
|
path: source.as_ref().to_owned(),
|
||||||
root_mod: false,
|
root_mod: false,
|
||||||
ignore: Vec::new(),
|
ignore: Vec::new(),
|
||||||
|
name: None,
|
||||||
|
nexus_id: None,
|
||||||
|
game: GameType::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_mod_info(
|
||||||
|
id: &str,
|
||||||
|
source: impl AsRef<Path>,
|
||||||
|
mod_info: &ModInfo,
|
||||||
|
file_id: u64,
|
||||||
|
) -> Self {
|
||||||
|
let mut normal = Self::new(id, source);
|
||||||
|
normal.name = Some(mod_info.name.clone());
|
||||||
|
normal.game = mod_info.get_game_type();
|
||||||
|
normal.nexus_id = Some(NexusID::new(mod_info.mod_id, file_id));
|
||||||
|
|
||||||
|
normal
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_id(mut self, id: &str) -> Self {
|
pub fn add_id(mut self, id: &str) -> Self {
|
||||||
self.id = id.to_owned();
|
self.id = id.to_owned();
|
||||||
self
|
self
|
||||||
@@ -55,8 +85,20 @@ impl ModConfig {
|
|||||||
pub fn ignore(&self) -> &[String] {
|
pub fn ignore(&self) -> &[String] {
|
||||||
&self.ignore
|
&self.ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<&str> {
|
||||||
|
self.name.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nexus_id(&self) -> Option<&NexusID> {
|
||||||
|
self.nexus_id.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_false(b: &bool) -> bool {
|
fn is_false(b: &bool) -> bool {
|
||||||
!b
|
!b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
|
||||||
|
t == &T::default()
|
||||||
|
}
|
||||||
|
|||||||
70
src/types/nexus_id.rs
Normal file
70
src/types/nexus_id.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct NexusID {
|
||||||
|
mod_id: u64,
|
||||||
|
file_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NexusID {
|
||||||
|
pub fn new(mod_id: u64, file_id: u64) -> Self {
|
||||||
|
Self { mod_id, file_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for NexusID {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s = format!("{}:{}", self.mod_id, self.file_id);
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for NexusID {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
let mut parts = s.split(':');
|
||||||
|
|
||||||
|
let mod_id = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| serde::de::Error::custom("missing first value"))
|
||||||
|
.and_then(|p| u64::from_str(p).map_err(serde::de::Error::custom))?;
|
||||||
|
|
||||||
|
let file_id = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| serde::de::Error::custom("missing second value"))
|
||||||
|
.and_then(|p| u64::from_str(p).map_err(serde::de::Error::custom))?;
|
||||||
|
|
||||||
|
if parts.next().is_some() {
|
||||||
|
return Err(serde::de::Error::custom("too many parts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { mod_id, file_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||||
|
struct Wrapper {
|
||||||
|
value: NexusID,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serde_roundtrip() {
|
||||||
|
let val = Wrapper {
|
||||||
|
value: NexusID::new(1234, 5678),
|
||||||
|
};
|
||||||
|
let serialized = toml::to_string(&val).unwrap();
|
||||||
|
let deserialized: Wrapper = toml::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(val, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,14 +57,30 @@ impl RootConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_to_file(&self) -> Result<(), ConfigReadWriteError> {
|
pub fn save_to_file(&self) -> Result<(), ConfigReadWriteError> {
|
||||||
|
debug!(
|
||||||
|
"Saving root_config to: {}",
|
||||||
|
self.self_path.to_string_lossy()
|
||||||
|
);
|
||||||
let content = toml::to_string_pretty(self)?;
|
let content = toml::to_string_pretty(self)?;
|
||||||
let mut file = fs::File::create(&self.self_path)?;
|
let mut file = fs::File::create(&self.self_path)?;
|
||||||
write!(file, "{}", content)?;
|
write!(file, "{}", content)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn game_by_id(&self, id: &str) -> Option<&Game> {
|
pub fn game_by_id(&self, id: &str) -> Option<Game> {
|
||||||
self.games.get(id)
|
self.games.get(id).map(|parsed_game| {
|
||||||
|
if parsed_game.install_location().is_relative() {
|
||||||
|
let abs_path = self.self_parent.join(parsed_game.install_location());
|
||||||
|
debug!(
|
||||||
|
"game path for {} is relative. Resolving to {}",
|
||||||
|
id,
|
||||||
|
abs_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
Game::new(abs_path, parsed_game.game_type())
|
||||||
|
} else {
|
||||||
|
parsed_game.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mod_by_id(&self, id: &str) -> Option<ModConfig> {
|
pub fn mod_by_id(&self, id: &str) -> Option<ModConfig> {
|
||||||
@@ -76,13 +92,19 @@ impl RootConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_instance_by_id(&self, id: &str) -> Result<ModdedInstance, ConfigReadWriteError> {
|
pub fn load_instance_by_id(&self, id: &str) -> Result<ModdedInstance, ConfigReadWriteError> {
|
||||||
|
debug!("Loading instance {}", id);
|
||||||
let conf = self
|
let conf = self
|
||||||
.instances
|
.instances
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or(ConfigReadWriteError::IDNotFound)?;
|
.ok_or(ConfigReadWriteError::IDNotFound)?;
|
||||||
|
|
||||||
if conf.path.is_relative() {
|
if conf.path.is_relative() {
|
||||||
ModdedInstance::load_from_file(self.self_parent.join(&conf.path))
|
let abs_path = self.self_parent.join(&conf.path);
|
||||||
|
debug!(
|
||||||
|
"instance path is relative. Resolving to {}",
|
||||||
|
abs_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
ModdedInstance::load_from_file(abs_path)
|
||||||
} else {
|
} else {
|
||||||
ModdedInstance::load_from_file(&conf.path)
|
ModdedInstance::load_from_file(&conf.path)
|
||||||
}
|
}
|
||||||
@@ -90,7 +112,12 @@ impl RootConfig {
|
|||||||
|
|
||||||
pub fn mod_location(&self) -> PathBuf {
|
pub fn mod_location(&self) -> PathBuf {
|
||||||
if self.mod_location.is_relative() {
|
if self.mod_location.is_relative() {
|
||||||
self.self_parent.join(&self.mod_location)
|
let abs_path = self.self_parent.join(&self.mod_location);
|
||||||
|
debug!(
|
||||||
|
"mod_location path is relative. Resolving to {}",
|
||||||
|
abs_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
abs_path
|
||||||
} else {
|
} else {
|
||||||
self.mod_location.clone()
|
self.mod_location.clone()
|
||||||
}
|
}
|
||||||
@@ -103,7 +130,12 @@ impl RootConfig {
|
|||||||
pub fn download_location(&self) -> Option<PathBuf> {
|
pub fn download_location(&self) -> Option<PathBuf> {
|
||||||
self.download_location.as_ref().map(|e| {
|
self.download_location.as_ref().map(|e| {
|
||||||
if e.is_relative() {
|
if e.is_relative() {
|
||||||
self.self_parent.join(e)
|
let abs_path = self.self_parent.join(e);
|
||||||
|
debug!(
|
||||||
|
"download_location path is relative. Resolving to {}",
|
||||||
|
abs_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
abs_path
|
||||||
} else {
|
} else {
|
||||||
e.clone()
|
e.clone()
|
||||||
}
|
}
|
||||||
@@ -118,11 +150,16 @@ struct InstancePointer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::types::GameType;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn create_config() -> RootConfig {
|
fn create_config() -> RootConfig {
|
||||||
RootConfig {
|
RootConfig {
|
||||||
games: HashMap::from([("sse".to_owned(), Game::new("/games/sse"))]),
|
games: HashMap::from([(
|
||||||
|
"sse".to_owned(),
|
||||||
|
Game::new("/games/sse", GameType::SkyrimSE),
|
||||||
|
)]),
|
||||||
mod_location: PathBuf::from("mods"),
|
mod_location: PathBuf::from("mods"),
|
||||||
download_location: Some(PathBuf::from("download")),
|
download_location: Some(PathBuf::from("download")),
|
||||||
nexus_api_key: Some("1234".to_owned()),
|
nexus_api_key: Some("1234".to_owned()),
|
||||||
@@ -151,6 +188,7 @@ mod tests {
|
|||||||
|
|
||||||
let unwraped = game.expect("Asserted before");
|
let unwraped = game.expect("Asserted before");
|
||||||
assert_eq!(unwraped.install_location(), "/games/sse");
|
assert_eq!(unwraped.install_location(), "/games/sse");
|
||||||
|
assert_eq!(unwraped.game_type(), GameType::SkyrimSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
108
src/unpacker.rs
108
src/unpacker.rs
@@ -1,28 +1,104 @@
|
|||||||
use std::{fs, path::Path};
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::{Ok, anyhow};
|
||||||
|
use log::error;
|
||||||
use crate::types::{ModConfig, RootConfig};
|
use zip::ZipArchive;
|
||||||
|
|
||||||
pub fn unpack(
|
|
||||||
root_config: &RootConfig,
|
|
||||||
id: &str,
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
) -> anyhow::Result<ModConfig> {
|
|
||||||
let extract_to = root_config.mod_location().join(id);
|
|
||||||
|
|
||||||
|
pub fn unpack(archive_path: impl AsRef<Path>, extract_to: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
if fs::exists(&extract_to)? {
|
if fs::exists(&extract_to)? {
|
||||||
return Err(anyhow!("File already exists"));
|
return Err(anyhow!(
|
||||||
|
"File already exists: {}",
|
||||||
|
extract_to.as_ref().to_string_lossy()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
unpack_7z_file(path, &extract_to)?;
|
match archive_path.as_ref().extension().and_then(|e| e.to_str()) {
|
||||||
|
Some("7z") => unpack_7z_file(archive_path, &extract_to),
|
||||||
|
Some("zip") => unpack_zip_file(archive_path, &extract_to),
|
||||||
|
Some("rar") => unpack_rar(archive_path, &extract_to),
|
||||||
|
Some(ext) => {
|
||||||
|
error!("Unsupported archive format: {}", ext);
|
||||||
|
Err(anyhow!("Unsupported archive format: {}", ext))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!(
|
||||||
|
"Failed to determine the file extension for {}",
|
||||||
|
&archive_path.as_ref().to_string_lossy()
|
||||||
|
);
|
||||||
|
Err(anyhow!("Failed to determine file extension"))
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
let new_mod = ModConfig::new(id, id);
|
unnest_dir(extract_to)?;
|
||||||
|
|
||||||
Ok(new_mod)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpack_7z_file(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()> {
|
fn unpack_7z_file(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
sevenz_rust::decompress_file(path, to)?;
|
sevenz_rust2::decompress_file(path, to)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unpack_zip_file(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
|
let file = fs::File::open(path)?;
|
||||||
|
let mut archive = ZipArchive::new(file)?;
|
||||||
|
archive.extract(to)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_rar(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
|
let mut archive = unrar::Archive::new(path.as_ref()).open_for_processing()?;
|
||||||
|
|
||||||
|
while let Some(header) = archive.read_header()? {
|
||||||
|
archive = header.extract_with_base(&to)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves a directorys content into the parent if it is the only dir
|
||||||
|
fn unnest_dir(path: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let Some(nested_dir) = check_nested_dir(path) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in fs::read_dir(&nested_dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let src = entry.path();
|
||||||
|
let dest = path.join(entry.file_name());
|
||||||
|
fs::rename(&src, &dest)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::remove_dir(&nested_dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the extracted archive has a single directory in it which contains the mod files
|
||||||
|
fn check_nested_dir(path: impl AsRef<Path>) -> Option<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let entries: Vec<_> = fs::read_dir(path).ok()?.filter_map(|e| e.ok()).collect();
|
||||||
|
|
||||||
|
if entries.len() == 1 {
|
||||||
|
let entry = &entries[0];
|
||||||
|
let entry_path = entry.path();
|
||||||
|
|
||||||
|
if entry_path
|
||||||
|
.file_name()
|
||||||
|
.is_some_and(|e| e == "Data" || e == "data")
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry_path.is_dir() {
|
||||||
|
return Some(entry_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub fn path_to_lowercase(path: impl AsRef<Path>) -> PathBuf {
|
|||||||
PathBuf::from(path.as_ref().to_string_lossy().to_lowercase())
|
PathBuf::from(path.as_ref().to_string_lossy().to_lowercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Searches for a path but ignores case. Returns the first it finds.
|
||||||
pub fn resolve_case_insensitive(
|
pub fn resolve_case_insensitive(
|
||||||
base: impl AsRef<Path>,
|
base: impl AsRef<Path>,
|
||||||
rel: impl AsRef<Path>,
|
rel: impl AsRef<Path>,
|
||||||
@@ -44,7 +45,7 @@ pub fn resolve_case_insensitive(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Use walkdir to walk all actual files in a dir
|
/// Use walkdir to walk all actual files in a dir
|
||||||
/// Returns early id any error occurs
|
/// Returns early if any error occurs
|
||||||
pub fn walk_all_files(
|
pub fn walk_all_files(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
) -> Result<impl Iterator<Item = walkdir::DirEntry>, walkdir::Error> {
|
) -> Result<impl Iterator<Item = walkdir::DirEntry>, walkdir::Error> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{collections::HashSet, error::Error, path::PathBuf};
|
use std::{collections::HashSet, error::Error, path::PathBuf};
|
||||||
|
|
||||||
use fomod_manager::{
|
use fomod_manager::{
|
||||||
instance::{files_to_install_mod, insert_mod_to_instance},
|
actions::{insert_mod_to_instance, resolve_files_for_install},
|
||||||
types::{Link, RootConfig},
|
types::{Link, RootConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,9 +20,9 @@ fn add_plain() -> Result<(), Box<dyn Error>> {
|
|||||||
let mod_to_install = root_config
|
let mod_to_install = root_config
|
||||||
.mod_by_id("add_test_plain")
|
.mod_by_id("add_test_plain")
|
||||||
.expect("Mod not found");
|
.expect("Mod not found");
|
||||||
let files_to_add = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
|
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
|
||||||
|
|
||||||
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
|
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
|
||||||
|
|
||||||
let installed_mods = instance.mods();
|
let installed_mods = instance.mods();
|
||||||
|
|
||||||
@@ -53,9 +53,9 @@ fn add_nested() -> Result<(), Box<dyn Error>> {
|
|||||||
let mod_to_install = root_config
|
let mod_to_install = root_config
|
||||||
.mod_by_id("add_test_nested")
|
.mod_by_id("add_test_nested")
|
||||||
.expect("Mod not found");
|
.expect("Mod not found");
|
||||||
let files_to_add = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
|
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
|
||||||
|
|
||||||
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
|
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
|
||||||
|
|
||||||
let installed_mods = instance.mods();
|
let installed_mods = instance.mods();
|
||||||
|
|
||||||
@@ -86,9 +86,9 @@ fn add_root() -> Result<(), Box<dyn Error>> {
|
|||||||
let mod_to_install = root_config
|
let mod_to_install = root_config
|
||||||
.mod_by_id("add_test_root")
|
.mod_by_id("add_test_root")
|
||||||
.expect("Mod not found");
|
.expect("Mod not found");
|
||||||
let files_to_add = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
|
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
|
||||||
|
|
||||||
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
|
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
|
||||||
|
|
||||||
let installed_mods = instance.mods();
|
let installed_mods = instance.mods();
|
||||||
|
|
||||||
@@ -117,9 +117,9 @@ fn add_filter() -> Result<(), Box<dyn Error>> {
|
|||||||
let mod_to_install = root_config
|
let mod_to_install = root_config
|
||||||
.mod_by_id("add_test_filter")
|
.mod_by_id("add_test_filter")
|
||||||
.expect("Mod not found");
|
.expect("Mod not found");
|
||||||
let files_to_add = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
|
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
|
||||||
|
|
||||||
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
|
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
|
||||||
|
|
||||||
let installed_mods = instance.mods();
|
let installed_mods = instance.mods();
|
||||||
|
|
||||||
|
|||||||
88
tests/data/fomod/moduleconfig/banana.xml
Normal file
88
tests/data/fomod/moduleconfig/banana.xml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
<moduleName>The BANANA Mod</moduleName>
|
||||||
|
<moduleImage path="fomod\images\banana.jpg" />
|
||||||
|
|
||||||
|
<installSteps>
|
||||||
|
<installStep name="THE FIRST OF MANY STEPS">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
|
||||||
|
|
||||||
|
<group name="Banana Types" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="1700s">
|
||||||
|
<description>Bananas from the 1700s were vastly different from what we see on the shelves today!</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Required" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Australian">
|
||||||
|
<description>Nobody knows why, but Australia has some WEIRD bananas.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Recommended" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Modern Worldwide">
|
||||||
|
<description>Ah, the modern Cavendish banana! How not-sweet it tastes! Do yourself a favor and get one from the 1700s.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Purple Bananas">
|
||||||
|
<description>~~CENSORED~~</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="CouldBeUsable" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Brown Bananas">
|
||||||
|
<description>Sorry, but you can't have feces.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="NotUsable" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group name="Banana Textures" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Base 1024x1024">
|
||||||
|
<description>These textures are used for anything we didn't downscale/upscale.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Required" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="2048x2048">
|
||||||
|
<description>2K is a comfortable resolution fot banana textures.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Recommended" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="4096x4096">
|
||||||
|
<description>4K might be a bit over the top, but hey.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="128x128">
|
||||||
|
<description>Looks awful.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="CouldBeUsable" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="0x0">
|
||||||
|
<description>Just... don't install the mod.</description>
|
||||||
|
<conditionFlags><flag name="1">1</flag></conditionFlags>
|
||||||
|
<typeDescriptor><type name="NotUsable" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
|
||||||
|
</config>
|
||||||
10
tests/data/fomod/moduleconfig/example_01.xml
Normal file
10
tests/data/fomod/moduleconfig/example_01.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
|
||||||
|
<moduleName>Example Mod</moduleName>
|
||||||
|
|
||||||
|
<requiredInstallFiles>
|
||||||
|
<file source="example.plugin"/>
|
||||||
|
</requiredInstallFiles>
|
||||||
|
|
||||||
|
</config>
|
||||||
30
tests/data/fomod/moduleconfig/example_02.xml
Normal file
30
tests/data/fomod/moduleconfig/example_02.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!-- For this second example, let's make use of dependencies.
|
||||||
|
Before starting the installation, dependencies
|
||||||
|
make sure the things you specify are in place.
|
||||||
|
-->
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
|
||||||
|
<moduleName>Example Mod</moduleName>
|
||||||
|
|
||||||
|
<!-- The "And" operator means that all dependencies
|
||||||
|
below this tag must be true for it to be true
|
||||||
|
as well. The other possible value is "Or".
|
||||||
|
-->
|
||||||
|
<moduleDependencies operator="And">
|
||||||
|
<fileDependency file="depend1.plugin" state="Active"/>
|
||||||
|
<dependencies operator="Or">
|
||||||
|
<fileDependency file="depend2v1.plugin" state="Active"/>
|
||||||
|
<fileDependency file="depend2v2.plugin" state="Active"/>
|
||||||
|
</dependencies>
|
||||||
|
</moduleDependencies>
|
||||||
|
|
||||||
|
<!-- Now before installing our lovely and empty
|
||||||
|
data file in requiredInstallFiles,
|
||||||
|
we need to make sure a few other plugins exist
|
||||||
|
-->
|
||||||
|
<requiredInstallFiles>
|
||||||
|
<file source="example.plugin"/>
|
||||||
|
</requiredInstallFiles>
|
||||||
|
|
||||||
|
</config>
|
||||||
86
tests/data/fomod/moduleconfig/example_03.xml
Normal file
86
tests/data/fomod/moduleconfig/example_03.xml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<!-- On the third example, we'll take a look at using
|
||||||
|
install steps to let users choose what to install
|
||||||
|
-->
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
|
||||||
|
<moduleName>Example Mod</moduleName>
|
||||||
|
|
||||||
|
<moduleDependencies operator="And">
|
||||||
|
<fileDependency file="depend1.plugin" state="Active"/>
|
||||||
|
<dependencies operator="Or">
|
||||||
|
<fileDependency file="depend2v1.plugin" state="Active"/>
|
||||||
|
<fileDependency file="depend2v2.plugin" state="Active"/>
|
||||||
|
</dependencies>
|
||||||
|
</moduleDependencies>
|
||||||
|
|
||||||
|
<!-- We'll no longer be using "requiredInstallFiles"
|
||||||
|
since we can now offer a choice between files
|
||||||
|
-->
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
<installStep name="Choose Option">
|
||||||
|
|
||||||
|
<!-- In 99.9% of cases you'll want to set
|
||||||
|
the 'order' attribute in "installSteps",
|
||||||
|
"optionalFileGroups" and "plugins" to
|
||||||
|
'Explicit'
|
||||||
|
-->
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
|
||||||
|
<!-- This tag collects options into separate
|
||||||
|
groups - useful if you want to have multiple
|
||||||
|
types of choices for the user in a single
|
||||||
|
step
|
||||||
|
-->
|
||||||
|
<group name="Select an option:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<!-- Each "plugin" tag represents a choice
|
||||||
|
the user can make.
|
||||||
|
-->
|
||||||
|
<plugin name="Option A">
|
||||||
|
|
||||||
|
<description>Select this to install Option A!</description>
|
||||||
|
|
||||||
|
<!-- Optional but recommended
|
||||||
|
-->
|
||||||
|
<image path="fomod/option_a.png"/>
|
||||||
|
|
||||||
|
<!-- The files/folders to install
|
||||||
|
-->
|
||||||
|
<files>
|
||||||
|
<folder source="option_a"/>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<!-- This describes what type the plugin is.
|
||||||
|
Most likely you'll choose between:
|
||||||
|
- 'Optional'
|
||||||
|
- 'Required'
|
||||||
|
- 'Recommended'
|
||||||
|
-->
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Recommended"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Option B">
|
||||||
|
<description>Select this to install Option B!</description>
|
||||||
|
<image path="fomod/option_b.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="option_b"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</optionalFileGroups>
|
||||||
|
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
|
||||||
|
</config>
|
||||||
141
tests/data/fomod/moduleconfig/example_04.xml
Normal file
141
tests/data/fomod/moduleconfig/example_04.xml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<!-- This time we'll take a look at multiple step
|
||||||
|
installs - flags and visiblity
|
||||||
|
-->
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
|
||||||
|
<moduleName>Example Mod</moduleName>
|
||||||
|
|
||||||
|
<moduleDependencies operator="And">
|
||||||
|
<fileDependency file="depend1.plugin" state="Active"/>
|
||||||
|
<dependencies operator="Or">
|
||||||
|
<fileDependency file="depend2v1.plugin" state="Active"/>
|
||||||
|
<fileDependency file="depend2v2.plugin" state="Active"/>
|
||||||
|
</dependencies>
|
||||||
|
</moduleDependencies>
|
||||||
|
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
|
||||||
|
<installStep name="Choose Option">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="Select an option:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Option A">
|
||||||
|
<description>Select this to install Option A!</description>
|
||||||
|
<image path="fomod/option_a.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="option_a"/>
|
||||||
|
</files>
|
||||||
|
<!-- conditionFlags and files have interchangeable
|
||||||
|
order and at least one of them needs to be
|
||||||
|
present.
|
||||||
|
|
||||||
|
conditionFlags sets flags whenever this plugin
|
||||||
|
is selected.
|
||||||
|
-->
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="option_a">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Recommended"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Option B">
|
||||||
|
<description>Select this to install Option B!</description>
|
||||||
|
<image path="fomod/option_b.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="option_b"/>
|
||||||
|
</files>
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="option_b">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
|
||||||
|
<installStep name="Choose Texture">
|
||||||
|
<!-- visible is a dependencies network that lets this
|
||||||
|
step appear only when it's conditions are met.
|
||||||
|
|
||||||
|
If they're not met, this step is skipped.
|
||||||
|
-->
|
||||||
|
<visible>
|
||||||
|
<flagDependency flag="option_a" value="selected"/>
|
||||||
|
</visible>
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="Select a texture:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Texture Blue">
|
||||||
|
<description>Select this to install Texture Blue!</description>
|
||||||
|
<image path="fomod/texture_blue.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="texture_blue_a"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Texture Red">
|
||||||
|
<description>Select this to install Texture Red!</description>
|
||||||
|
<image path="fomod/texture_red.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="texture_red_a"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
|
||||||
|
<installStep name="Choose Texture">
|
||||||
|
<visible>
|
||||||
|
<flagDependency flag="option_b" value="selected"/>
|
||||||
|
</visible>
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="Select a texture:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Texture Blue">
|
||||||
|
<description>Select this to install Texture Blue!</description>
|
||||||
|
<image path="fomod/texture_blue.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="texture_blue_b"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Texture Red">
|
||||||
|
<description>Select this to install Texture Red!</description>
|
||||||
|
<image path="fomod/texture_red.png"/>
|
||||||
|
<files>
|
||||||
|
<folder source="texture_red_b"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
|
||||||
|
</installSteps>
|
||||||
|
|
||||||
|
</config>
|
||||||
133
tests/data/fomod/moduleconfig/example_05.xml
Normal file
133
tests/data/fomod/moduleconfig/example_05.xml
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
|
||||||
|
<moduleName>Example Mod</moduleName>
|
||||||
|
|
||||||
|
<moduleDependencies operator="And">
|
||||||
|
<fileDependency file="depend1.plugin" state="Active"/>
|
||||||
|
<dependencies operator="Or">
|
||||||
|
<fileDependency file="depend2v1.plugin" state="Active"/>
|
||||||
|
<fileDependency file="depend2v2.plugin" state="Active"/>
|
||||||
|
</dependencies>
|
||||||
|
</moduleDependencies>
|
||||||
|
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
<installStep name="Choose Option">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
|
||||||
|
<group name="Select an option:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Option A">
|
||||||
|
<description>Select this to install Option A!</description>
|
||||||
|
<image path="fomod/option_a.png"/>
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="option_a">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Recommended"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Option B">
|
||||||
|
<description>Select this to install Option B!</description>
|
||||||
|
<image path="fomod/option_b.png"/>
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="option_b">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- Since we're installing everything in
|
||||||
|
conditionalFileInstalls we're free to
|
||||||
|
make all selections in a single step.
|
||||||
|
-->
|
||||||
|
<group name="Select a texture:" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="Texture Blue">
|
||||||
|
<description>Select this to install Texture Blue!</description>
|
||||||
|
<image path="fomod/texture_blue.png"/>
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="texture_blue">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Texture Red">
|
||||||
|
<description>Select this to install Texture Red!</description>
|
||||||
|
<image path="fomod/texture_red.png"/>
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="texture_red">selected</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
|
||||||
|
<!-- This is where we're installing everything.
|
||||||
|
-->
|
||||||
|
<conditionalFileInstalls>
|
||||||
|
<patterns>
|
||||||
|
<!-- Each pattern is a piece of the matrix
|
||||||
|
eseentially linking a set of dependencies
|
||||||
|
to a set of files to install.
|
||||||
|
-->
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="option_a" value="selected"/>
|
||||||
|
<flagDependency flag="texture_blue" value="selected"/>
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="option_a"/>
|
||||||
|
<folder source="texture_blue_a"/>
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="option_a" value="selected"/>
|
||||||
|
<flagDependency flag="texture_red" value="selected"/>
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="option_a"/>
|
||||||
|
<folder source="texture_red_a"/>
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="option_b" value="selected"/>
|
||||||
|
<flagDependency flag="texture_blue" value="selected"/>
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="option_b"/>
|
||||||
|
<folder source="texture_blue_b"/>
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="option_b" value="selected"/>
|
||||||
|
<flagDependency flag="texture_red" value="selected"/>
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="option_b"/>
|
||||||
|
<folder source="texture_red_b"/>
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
</patterns>
|
||||||
|
</conditionalFileInstalls>
|
||||||
|
|
||||||
|
</config>
|
||||||
118
tests/data/fomod/moduleconfig/ineed.xml
Normal file
118
tests/data/fomod/moduleconfig/ineed.xml
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
<moduleName>iNeed - Food, Water and Sleep - Continued</moduleName>
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
<installStep name="Options">
|
||||||
|
<optionalFileGroups>
|
||||||
|
<group name="0. Core" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Core Files">
|
||||||
|
<description>
|
||||||
|
Contains the main mod.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\screenshot.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="00 Core" destination="" priority="0"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Required"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
<group name="1. Extended" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Full | Requires AE and Creation Club Fishing">
|
||||||
|
<description>
|
||||||
|
Requires AE and Creation Club Fishing. This option is not compatible with other mods that modify vanilla food, recipes and with 'Survival' mode! Enables snow collection from medium to large exterior snow drifts simply by activating them. Removes magic effect buffs and debuffs from most food and drink items. Removes the Salt Pile ingredient from cooked meat. Rebalances soup recipes by adding a water ingredient requirement and by changing the number of soups produced from 1 to 2. Innkeepers will now sometimes sell soups and Hearthfire foods.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\extended.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="01 Extended" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="No Food Changes | Requires AE and Creation Club Fishing">
|
||||||
|
<description>
|
||||||
|
Requires AE and Creation Club Fishing. This option is not compatible with other mods that modify vanilla recipes and with 'Survival' mode! Enables snow collection from medium to large exterior snow drifts simply by activating them. Removes the Salt Pile ingredient from cooked meat. Rebalances soup recipes by adding a water ingredient requirement and by changing the number of soups produced from 1 to 2. Innkeepers will now sometimes sell soups and Hearthfire foods.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\extended.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="02 Extended - No Food" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="No Food / No Recipe Changes | Requires AE and Creation Club Fishing">
|
||||||
|
<description>
|
||||||
|
Requires AE and Creation Club Fishing. Enables snow collection from medium to large exterior snow drifts simply by activating them. This option is compatible with all other mods that change "vanilla" cooking recipes, such as CACO or Cooking Expanded. Install it if such mods are installed, for better compatibility with iNeed.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\extended.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="03 Extended - No Food - No Recipes" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="None">
|
||||||
|
<description>
|
||||||
|
iNeed - Extended will not be installed.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\extended.jpg"/>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
<group name="2. Compatibility" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Nordic Snow">
|
||||||
|
<description>
|
||||||
|
Select this option if you have any version of iNeed - Extended installed along with Nordic Snow. This patch will match up the snow drift textures with the rest of the landscape modified by Nordic Snow.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\snow.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="10 Nordic Snow" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="NobleSkyrimMod">
|
||||||
|
<description>
|
||||||
|
Select this option if you have any version of iNeed - Extended installed along with NobleSkyrimMod. This patch will match up the snow drift textures with the rest of the landscape modified by NobleSkyrimMod.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\snow.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="11 NobleSkyrimMod" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
<group name="Optionals" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Dangerous Diseases">
|
||||||
|
<description>
|
||||||
|
All non-transformative diseases will be more unique, harder to cure and progress through 4 deadlier stages at random intervals. See mod description for more information.
|
||||||
|
</description>
|
||||||
|
<image path="FOMod\disease.jpg"/>
|
||||||
|
<files>
|
||||||
|
<folder source="12 Dangerous Diseases Converted" destination="" priority="1"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
</config>
|
||||||
67
tests/data/fomod/moduleconfig/po3tweaks.xml
Normal file
67
tests/data/fomod/moduleconfig/po3tweaks.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
<moduleName>powerofthree's Tweaks</moduleName>
|
||||||
|
<requiredInstallFiles>
|
||||||
|
<folder source="Required" destination="" />
|
||||||
|
</requiredInstallFiles>
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
<installStep name="Main">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="DLL" type="SelectExactlyOne">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="SSE v1.6.629+ ("Anniversary Edition")">
|
||||||
|
<description>Select this if you are using Skyrim Anniversary Edition v1.6.629 or higher.</description>
|
||||||
|
<files>
|
||||||
|
<folder source="AE/SKSE/Plugins" destination="SKSE/Plugins" priority="0" />
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<dependencyType>
|
||||||
|
<defaultType name="Optional" />
|
||||||
|
<patterns>
|
||||||
|
<pattern>
|
||||||
|
<dependencies>
|
||||||
|
<gameDependency version="1.6" />
|
||||||
|
</dependencies>
|
||||||
|
<type name="Recommended" />
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies>
|
||||||
|
<gameDependency version="1.5" />
|
||||||
|
</dependencies>
|
||||||
|
<type name="Optional" />
|
||||||
|
</pattern>
|
||||||
|
</patterns>
|
||||||
|
</dependencyType>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="SSE v1.5.97 ("Special Edition")">
|
||||||
|
<description>Select this if you are using Skyrim Special Edition v1.5.97.</description>
|
||||||
|
<files>
|
||||||
|
<folder source="SE/SKSE/Plugins" destination="SKSE/Plugins" priority="0" />
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<dependencyType>
|
||||||
|
<defaultType name="Optional" />
|
||||||
|
<patterns>
|
||||||
|
<pattern>
|
||||||
|
<dependencies>
|
||||||
|
<gameDependency version="1.6" />
|
||||||
|
</dependencies>
|
||||||
|
<type name="Optional" />
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies>
|
||||||
|
<gameDependency version="1.5" />
|
||||||
|
</dependencies>
|
||||||
|
<type name="Recommended" />
|
||||||
|
</pattern>
|
||||||
|
</patterns>
|
||||||
|
</dependencyType>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
</config>
|
||||||
171
tests/data/fomod/moduleconfig/starui.xml
Normal file
171
tests/data/fomod/moduleconfig/starui.xml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
<moduleName>StarUI Inventory</moduleName>
|
||||||
|
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
|
||||||
|
<installStep name="Select installation options">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="Main files" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
|
||||||
|
<plugin name="StarUI Inventory">
|
||||||
|
<description>StarUI Inventory improves all inventory screens for use on a PC. Compact display style. More details in sortable columns. Item category icons. Category as left sidebar. Many quality of life features!</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<files />
|
||||||
|
<typeDescriptor><type name="Required" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group name="Mod Manager" type="SelectExactlyOne">
|
||||||
|
<plugins order='Explicit'>
|
||||||
|
<plugin name="Vortex">
|
||||||
|
<description>Select this if you use Vortex</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="flag_vortex">Active</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<files>
|
||||||
|
<folder source="Interface" destination="Data\Interface" />
|
||||||
|
</files>
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Mod Organizer 2">
|
||||||
|
<description>Select this if you use Mod Organizer 2.</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="flag_mo2">Active</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<files>
|
||||||
|
<folder source="Interface" destination="Interface" />
|
||||||
|
</files>
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<group name="FPS (Frames Per Second)" type="SelectExactlyOne">
|
||||||
|
<plugins order='Explicit'>
|
||||||
|
<plugin name="30 FPS - Vanilla">
|
||||||
|
<description>Vanilla interface FPS. As like in the original game.</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="flag_30fps">Active</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="60 FPS - Smooth and stable">
|
||||||
|
<description>Doubles the default interface FPS. Smoother and more responsive.</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="flag_60fps">Active</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor><type name="Recommended" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="120 FPS - High-FPS (may cause crashes)">
|
||||||
|
<description>High-FPS version. This version needs an appropiate monitor to be used. 
|
||||||
|
WARNING: Using 120FPS may cause the game to crash, as the game engine is not programmed for such high interface FPS rates.
|
||||||
|

|
||||||
|
USE AT YOUR OWN RISK.
|
||||||
|
</description>
|
||||||
|
<image path="fomod\images\StarUI Inventory Teaser.jpg" />
|
||||||
|
<conditionFlags>
|
||||||
|
<flag name="flag_120fps">Active</flag>
|
||||||
|
</conditionFlags>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor><type name="Optional" /></typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</optionalFileGroups>
|
||||||
|
|
||||||
|
</installStep>
|
||||||
|
|
||||||
|
<installStep name="README">
|
||||||
|
<optionalFileGroups order="Explicit">
|
||||||
|
<group name="Please read the notes" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Requires Archive Invalidation">
|
||||||
|
<description>You will need to enable Archive Invalidation to load loose files.
|
||||||
|
If you haven't done that yet, see the mod page for detailed instructions.
</description>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Required" />
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="INI: Settings, settings, settings">
|
||||||
|
<description>You can configure many different settings in the file Interface\StarUI Inventory.ini .
|
||||||
|
Every settings is described in the file, so you can easily adapt the whole mod to your liking.
</description>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Required" />
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin name="Configuration done">
|
||||||
|
<description>Ready for installation.
|
||||||
|
If you are updating, make sure you have a backup of your StarUI Inventory.ini to keep your settings.
</description>
|
||||||
|
<files />
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Required" />
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
</installSteps>
|
||||||
|
|
||||||
|
<conditionalFileInstalls>
|
||||||
|
<patterns>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="flag_vortex" value="Active" />
|
||||||
|
<flagDependency flag="flag_30fps" value="Active" />
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="Optional\30fps\Interface" destination="Data\Interface" />
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="flag_mo2" value="Active" />
|
||||||
|
<flagDependency flag="flag_30fps" value="Active" />
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="Optional\30fps\Interface" destination="Interface" />
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="flag_vortex" value="Active" />
|
||||||
|
<flagDependency flag="flag_120fps" value="Active" />
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="Optional\120fps\Interface" destination="Data\Interface" />
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<dependencies operator="And">
|
||||||
|
<flagDependency flag="flag_mo2" value="Active" />
|
||||||
|
<flagDependency flag="flag_120fps" value="Active" />
|
||||||
|
</dependencies>
|
||||||
|
<files>
|
||||||
|
<folder source="Optional\120fps\Interface" destination="Interface" />
|
||||||
|
</files>
|
||||||
|
</pattern>
|
||||||
|
</patterns>
|
||||||
|
</conditionalFileInstalls>
|
||||||
|
|
||||||
|
</config>
|
||||||
60
tests/data/fomod/moduleconfig/trade_barter.xml
Normal file
60
tests/data/fomod/moduleconfig/trade_barter.xml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||||
|
<moduleName>Trade and Barter - Patches</moduleName>
|
||||||
|
<moduleImage path="FOMOD\header.jpg"/>
|
||||||
|
|
||||||
|
<installSteps order="Explicit">
|
||||||
|
<installStep name="Trade and Barter - Patches ESPLite">
|
||||||
|
<optionalFileGroups>
|
||||||
|
<group name="Core" type="SelectAny">
|
||||||
|
<plugins order="Explicit">
|
||||||
|
<plugin name="Beyond Bruma Patch">
|
||||||
|
<description>
|
||||||
|
ESPLite version
|
||||||
|
</description>
|
||||||
|
<files>
|
||||||
|
<folder source="01_bruma" destination="" priority="0"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="YASH Patch">
|
||||||
|
<description>
|
||||||
|
ESPLite version
|
||||||
|
</description>
|
||||||
|
<files>
|
||||||
|
<folder source="02_yash" destination="" priority="0"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="Keld-Nar Patch">
|
||||||
|
<description>
|
||||||
|
ESPLite version
|
||||||
|
</description>
|
||||||
|
<files>
|
||||||
|
<folder source="00_keld" destination="" priority="0"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
<plugin name="Priest Vendors Patch">
|
||||||
|
<description>
|
||||||
|
ESPLite version
|
||||||
|
</description>
|
||||||
|
<files>
|
||||||
|
<folder source="03_vendor" destination="" priority="0"/>
|
||||||
|
</files>
|
||||||
|
<typeDescriptor>
|
||||||
|
<type name="Optional"/>
|
||||||
|
</typeDescriptor>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</group>
|
||||||
|
</optionalFileGroups>
|
||||||
|
</installStep>
|
||||||
|
|
||||||
|
</installSteps>
|
||||||
|
</config>
|
||||||
@@ -10,171 +10,99 @@ load_order = [
|
|||||||
"ccBGSSSE037-Curios.esl",
|
"ccBGSSSE037-Curios.esl",
|
||||||
"ccBGSSSE025-AdvDSGS.esm",
|
"ccBGSSSE025-AdvDSGS.esm",
|
||||||
"_ResourcePack.esl",
|
"_ResourcePack.esl",
|
||||||
|
"RaceMenu.esp",
|
||||||
"SkyUI_SE.esp",
|
"SkyUI_SE.esp",
|
||||||
|
"RaceMenuPlugin.esp",
|
||||||
]
|
]
|
||||||
game_file_overrides = [[
|
game_file_overrides = [
|
||||||
"skse64_loader.exe",
|
"skse64_loader.exe -> SkyrimSELauncher.exe"
|
||||||
"SkyrimSELauncher.exe",
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[mods]]
|
|
||||||
id = "skyui"
|
|
||||||
files = [
|
|
||||||
[
|
|
||||||
"SkyUI_SE.esp",
|
|
||||||
"Data/SkyUI_SE.esp",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"SkyUI_SE.bsa",
|
|
||||||
"Data/SkyUI_SE.bsa",
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
priority = 0
|
|
||||||
|
|
||||||
[[mods]]
|
[[mods]]
|
||||||
id = "skse"
|
id = "skse"
|
||||||
files = [
|
files = [
|
||||||
[
|
"Data/Scripts/math.pex",
|
||||||
"Data/Scripts/actorbase.pex",
|
"Data/Scripts/form.pex",
|
||||||
"Data/Scripts/actorbase.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/weather.pex",
|
|
||||||
"Data/Scripts/weather.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"skse64_loader.exe",
|
|
||||||
"skse64_loader.exe",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/headpart.pex",
|
|
||||||
"Data/Scripts/headpart.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/soulgem.pex",
|
"Data/Scripts/soulgem.pex",
|
||||||
"Data/Scripts/soulgem.pex",
|
"Data/Scripts/formlist.pex",
|
||||||
],
|
"Data/Scripts/stringutil.pex",
|
||||||
[
|
"Data/Scripts/colorcomponent.pex",
|
||||||
"Data/Scripts/modevent.pex",
|
"Data/Scripts/quest.pex",
|
||||||
"Data/Scripts/modevent.pex",
|
"Data/Scripts/faction.pex",
|
||||||
],
|
"Data/Scripts/combatstyle.pex",
|
||||||
[
|
"Data/Scripts/actorbase.pex",
|
||||||
"Data/Scripts/actorvalueinfo.pex",
|
|
||||||
"Data/Scripts/actorvalueinfo.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/book.pex",
|
|
||||||
"Data/Scripts/book.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/potion.pex",
|
"Data/Scripts/potion.pex",
|
||||||
"Data/Scripts/potion.pex",
|
"Data/Scripts/actor.pex",
|
||||||
],
|
"Data/Scripts/game.pex",
|
||||||
[
|
"Data/Scripts/armor.pex",
|
||||||
"Data/Scripts/spell.pex",
|
"Data/Scripts/headpart.pex",
|
||||||
"Data/Scripts/spell.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/perk.pex",
|
|
||||||
"Data/Scripts/perk.pex",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Data/Scripts/objectreference.pex",
|
"Data/Scripts/objectreference.pex",
|
||||||
"Data/Scripts/objectreference.pex",
|
"Data/Scripts/weapon.pex",
|
||||||
],
|
"Data/Scripts/perk.pex",
|
||||||
|
"Data/Scripts/constructibleobject.pex",
|
||||||
|
"Data/Scripts/armoraddon.pex",
|
||||||
|
"Data/Scripts/textureset.pex",
|
||||||
|
"Data/Scripts/scroll.pex",
|
||||||
|
"Data/Scripts/actorvalueinfo.pex",
|
||||||
|
"Data/Scripts/equipslot.pex",
|
||||||
|
"Data/Scripts/art.pex",
|
||||||
|
"Data/Scripts/colorform.pex",
|
||||||
|
"Data/Scripts/weather.pex",
|
||||||
|
"Data/Scripts/gamedata.pex",
|
||||||
|
"Data/Scripts/skse.pex",
|
||||||
|
"Data/Scripts/sound.pex",
|
||||||
|
"Data/Scripts/formtype.pex",
|
||||||
|
"Data/Scripts/spawnertask.pex",
|
||||||
|
"Data/Scripts/netimmerse.pex",
|
||||||
|
"Data/Scripts/ingredient.pex",
|
||||||
|
"Data/Scripts/book.pex",
|
||||||
|
"Data/Scripts/ui.pex",
|
||||||
|
"Data/Scripts/leveleditem.pex",
|
||||||
|
"Data/Scripts/spell.pex",
|
||||||
|
"Data/Scripts/leveledspell.pex",
|
||||||
|
"Data/Scripts/modevent.pex",
|
||||||
|
"Data/Scripts/keyword.pex",
|
||||||
|
"Data/Scripts/activemagiceffect.pex",
|
||||||
|
"Data/Scripts/utility.pex",
|
||||||
|
"Data/Scripts/shout.pex",
|
||||||
|
"Data/Scripts/input.pex",
|
||||||
|
"Data/Scripts/race.pex",
|
||||||
|
"Data/Scripts/sounddescriptor.pex",
|
||||||
|
"Data/Scripts/wornobject.pex",
|
||||||
|
"Data/Scripts/ammo.pex",
|
||||||
|
"Data/Scripts/defaultobjectmanager.pex",
|
||||||
|
"Data/Scripts/camera.pex",
|
||||||
|
"Data/Scripts/apparatus.pex",
|
||||||
|
"skse64_1_6_1170.dll",
|
||||||
|
"Data/Scripts/magiceffect.pex",
|
||||||
|
"Data/Scripts/location.pex",
|
||||||
|
"Data/Scripts/alias.pex",
|
||||||
|
"Data/Scripts/treeobject.pex",
|
||||||
|
"Data/Scripts/leveledactor.pex",
|
||||||
|
"Data/Scripts/enchantment.pex",
|
||||||
|
"Data/Scripts/uicallback.pex",
|
||||||
|
"Data/Scripts/flora.pex",
|
||||||
|
"Data/Scripts/outfit.pex",
|
||||||
|
"Data/Scripts/cell.pex",
|
||||||
]
|
]
|
||||||
priority = 0
|
priority = 0
|
||||||
|
|
||||||
[[mods]]
|
[[mods]]
|
||||||
id = "deadly_spells"
|
id = "SkyUI-12604-35407"
|
||||||
files = [
|
files = [
|
||||||
[
|
"SkyUI_SE.bsa -> Data/SkyUI_SE.bsa",
|
||||||
"000 Core Files/textures/impactdecals/decalsnowhole01_n.dds",
|
"SkyUI_SE.esp -> Data/SkyUI_SE.esp",
|
||||||
"Data/textures/impactdecals/decalsnowhole01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"40 Two Fire Esp/textures/impactdecals/decalflamespread01_g.dds",
|
|
||||||
"Data/textures/impactdecals/decalflamespread01_g.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsparkburn01_g.dds",
|
|
||||||
"Data/textures/impactdecals/decalsparkburn01_g.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"40 Two Fire Esp/textures/impactdecals/decalflamespread01.dds",
|
|
||||||
"Data/textures/impactdecals/decalflamespread01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalfrostimpact01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalfrostimpact01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalspitimpact01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalspitimpact01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"40 Two Fire Esp/DeadlySpellImpacts - Two Fire.esp",
|
|
||||||
"Data/DeadlySpellImpacts - Two Fire.esp",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsnowmelt01.dds",
|
|
||||||
"Data/textures/impactdecals/decalsnowmelt01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalspitimpact01.dds",
|
|
||||||
"Data/textures/impactdecals/decalspitimpact01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsnowhole01.dds",
|
|
||||||
"Data/textures/impactdecals/decalsnowhole01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsparkburn01.dds",
|
|
||||||
"Data/textures/impactdecals/decalsparkburn01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"10 Fire Cracks/textures/impactdecals/decalflameburn01_g.dds",
|
|
||||||
"Data/textures/impactdecals/decalflameburn01_g.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsnowmelt01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalsnowmelt01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalfrostimpact01.dds",
|
|
||||||
"Data/textures/impactdecals/decalfrostimpact01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"10 Fire Cracks/textures/impactdecals/decalflameburn01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalflameburn01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsnowmelt01_g.dds",
|
|
||||||
"Data/textures/impactdecals/decalsnowmelt01_g.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsparkburn01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalsparkburn01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/textures/impactdecals/decalsnowhole01_g.dds",
|
|
||||||
"Data/textures/impactdecals/decalsnowhole01_g.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"40 Two Fire Esp/Manual Installation of the Two Fire Option.txt",
|
|
||||||
"Data/Manual Installation of the Two Fire Option.txt",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"10 Fire Cracks/textures/impactdecals/decalflameburn01.dds",
|
|
||||||
"Data/textures/impactdecals/decalflameburn01.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"40 Two Fire Esp/textures/impactdecals/decalflamespread01_n.dds",
|
|
||||||
"Data/textures/impactdecals/decalflamespread01_n.dds",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"000 Core Files/DeadlySpellImpacts.esp",
|
|
||||||
"Data/DeadlySpellImpacts.esp",
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
priority = 1
|
priority = 0
|
||||||
|
|
||||||
|
[[mods]]
|
||||||
|
id = "racemenu-19080-465102"
|
||||||
|
files = [
|
||||||
|
"RaceMenu.esp -> Data/RaceMenu.esp",
|
||||||
|
"SKSE/Plugins/skee64.ini -> Data/SKSE/Plugins/skee64.ini",
|
||||||
|
"RaceMenu.bsa -> Data/RaceMenu.bsa",
|
||||||
|
"RaceMenuPlugin.esp -> Data/RaceMenuPlugin.esp",
|
||||||
|
"SKSE/Plugins/skee64.dll -> Data/SKSE/Plugins/skee64.dll",
|
||||||
|
]
|
||||||
|
priority = 0
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ nexus_api_key = "1234"
|
|||||||
|
|
||||||
[games.example_game]
|
[games.example_game]
|
||||||
path = "/home/user/games/sse"
|
path = "/home/user/games/sse"
|
||||||
|
kind = "sse"
|
||||||
|
|
||||||
[games.sse]
|
[games.sse]
|
||||||
path = "games/sse"
|
path = "games/sse"
|
||||||
|
kind = "unkown"
|
||||||
|
|
||||||
[instances.example1]
|
[instances.example1]
|
||||||
path = "example1.toml"
|
path = "example1.toml"
|
||||||
|
|||||||
42
tests/fomod_test.rs
Normal file
42
tests/fomod_test.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fomod_manager::fomod::{self, FOModError};
|
||||||
|
|
||||||
|
fn get_parent() -> PathBuf {
|
||||||
|
PathBuf::from(file!()).parent().unwrap().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_xml(filename: &str) -> PathBuf {
|
||||||
|
get_parent().join(format!("data/fomod/moduleconfig/{}", filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err_to_string(e: FOModError) -> String {
|
||||||
|
match e {
|
||||||
|
FOModError::Io(error) => format!("IO: {:?}", error),
|
||||||
|
FOModError::Parse(de_error) => match de_error {
|
||||||
|
quick_xml::DeError::UnexpectedStart(items) => {
|
||||||
|
format!("UnexpectedStart: {}", str::from_utf8(&items).unwrap())
|
||||||
|
}
|
||||||
|
_ => format!("Other: {:?}", de_error),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
for xml in [
|
||||||
|
"ineed.xml",
|
||||||
|
"trade_barter.xml",
|
||||||
|
"starui.xml",
|
||||||
|
"example_01.xml",
|
||||||
|
"example_02.xml",
|
||||||
|
"example_03.xml",
|
||||||
|
"example_04.xml",
|
||||||
|
"example_05.xml",
|
||||||
|
"banana.xml",
|
||||||
|
"po3tweaks.xml"
|
||||||
|
] {
|
||||||
|
fomod::Config::load_from_file(get_xml(xml))
|
||||||
|
.unwrap_or_else(|e| panic!("Parse for {xml} with {}", err_to_string(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tests/game_test.rs
Normal file
27
tests/game_test.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fomod_manager::types::RootConfig;
|
||||||
|
|
||||||
|
fn get_parent() -> PathBuf {
|
||||||
|
PathBuf::from(file!()).parent().unwrap().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_root() -> RootConfig {
|
||||||
|
RootConfig::load_from_file(get_parent().join("data/root_config_complex.toml")).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_gamefiles() {
|
||||||
|
let root_config = load_root();
|
||||||
|
|
||||||
|
let game = root_config.game_by_id("sse").expect("No game found");
|
||||||
|
|
||||||
|
let links = game.export_links().expect("Failed to export game links");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
links.iter().all(|e| e.src().is_absolute()),
|
||||||
|
"Link src is not absolute"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(links.len(), 4, "Not all files linked");
|
||||||
|
}
|
||||||
@@ -38,14 +38,14 @@ fn parse_complex() {
|
|||||||
let unwraped = inst.expect("Asserted before");
|
let unwraped = inst.expect("Asserted before");
|
||||||
|
|
||||||
assert_eq!(unwraped.game_id(), "sse");
|
assert_eq!(unwraped.game_id(), "sse");
|
||||||
assert_eq!(unwraped.load_order().len(), 11);
|
assert_eq!(unwraped.load_order().len(), 13);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unwraped.game_file_overrides().first().unwrap(),
|
unwraped.game_file_overrides().first().unwrap(),
|
||||||
&Link::new("skse64_loader.exe", "SkyrimSELauncher.exe")
|
&Link::new("skse64_loader.exe", "SkyrimSELauncher.exe")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(unwraped.mods().len(), 3);
|
assert_eq!(unwraped.mods().len(), 3);
|
||||||
let test_mod = unwraped.mods().iter().find(|e| e.mod_id() == "skyui");
|
let test_mod = unwraped.mods().iter().find(|e| e.mod_id() == "SkyUI-12604-35407");
|
||||||
assert!(test_mod.is_some());
|
assert!(test_mod.is_some());
|
||||||
|
|
||||||
assert_eq!(test_mod.unwrap().priority(), 0);
|
assert_eq!(test_mod.unwrap().priority(), 0);
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ fn parse_complex() {
|
|||||||
"Installed game wrong path"
|
"Installed game wrong path"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
config
|
||||||
|
.game_by_id("sse")
|
||||||
|
.is_some_and(|e| e.install_location().is_absolute()),
|
||||||
|
"Relative game path was not resolved to absolute"
|
||||||
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
config
|
config
|
||||||
.game_by_id("sse")
|
.game_by_id("sse")
|
||||||
|
|||||||
Reference in New Issue
Block a user