From 31bf3223c6ac84f830ab6b66e4c50e87cc4ba4bf Mon Sep 17 00:00:00 2001 From: Moritz Baron Date: Tue, 12 Mar 2024 12:21:45 +0100 Subject: [PATCH] add desc and ver predicates closes #4 --- Cargo.lock | 38 ++ Cargo.toml | 7 +- base_rules.json | 900 ++++++++++----------------------- gui/Cargo.toml | 2 + src/expressions.rs | 93 +++- src/lib.rs | 223 ++++++-- src/parser.rs | 62 ++- tests/integration_tests.rs | 21 + tests/test1.esp | Bin 0 -> 362 bytes tests/unit_expression_tests.rs | 181 +++++++ tests/unit_parser_tests.rs | 8 +- user_rules.json | 20 +- 12 files changed, 823 insertions(+), 732 deletions(-) create mode 100644 tests/test1.esp diff --git a/Cargo.lock b/Cargo.lock index 505e8b8..d619670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1327,6 +1327,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lenient_semver" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de8de3f4f3754c280ce1c8c42ed8dd26a9c8385c2e5ad4ec5a77e774cea9c1ec" +dependencies = [ + "lenient_semver_parser", + "semver", +] + +[[package]] +name = "lenient_semver_parser" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f650c1d024ddc26b4bb79c3076b30030f2cf2b18292af698c81f7337a64d7d6" +dependencies = [ + "lenient_semver_version_builder", + "semver", +] + +[[package]] +name = "lenient_semver_version_builder" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9049f8ff49f75b946f95557148e70230499c8a642bf2d6528246afc7d0282d17" +dependencies = [ + "semver", +] + [[package]] name = "libc" version = "0.2.153" @@ -1777,6 +1806,7 @@ dependencies = [ "clap", "env_logger", "filetime", + "lenient_semver", "log", "openmw-cfg", "pretty_assertions", @@ -1785,6 +1815,7 @@ dependencies = [ "reqwest", "rust-ini 0.20.0", "seahash", + "semver", "serde", "serde_json", "toposort-scc", @@ -1794,6 +1825,7 @@ dependencies = [ name = "plox_gui" version = "0.2.0" dependencies = [ + "byteorder", "eframe", "egui", "log", @@ -2181,6 +2213,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.197" diff --git a/Cargo.toml b/Cargo.toml index 7866a75..05675d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ documentation = "" log = "0.4" env_logger = "0.11" serde = { version = "1.0", features = ["derive"] } +byteorder = "1.5" [package] name = "plox" @@ -23,17 +24,19 @@ description.workspace = true documentation.workspace = true [dependencies] +openmw-cfg = "0.5" # move to feature flag clap = { version = "4.1", features = ["derive"] } toposort-scc = "0.5" -byteorder = "1.5" rust-ini = "0.20" regex = "1.10" reqwest = { version = "0.11", features = ["blocking", "json"] } serde_json = "1.0" seahash = "4.1" filetime = "0.2" -openmw-cfg = "0.5" # move to feature flag +semver = "1.0" +lenient_semver = "0.4" +byteorder = { workspace = true } log = { workspace = true } env_logger = { workspace = true } serde = { workspace = true } diff --git a/base_rules.json b/base_rules.json index c677ddf..a303191 100644 --- a/base_rules.json +++ b/base_rules.json @@ -2531,12 +2531,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "rise of house telvanni.esm" - } + "item": "rise of house telvanni.esm" }, "operator": "Greater", - "version": "1.51" + "version": "1.51.0" } } ] @@ -2937,9 +2935,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -2948,9 +2944,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -3911,9 +3905,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "animated_morrowind - expanded.esp" - } + "item": "animated_morrowind - expanded.esp" }, "regex": "spirithawke edit", "is_negated": true @@ -3930,9 +3922,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "animated morrowind - expanded - mca_patch.esp" - } + "item": "animated morrowind - expanded - mca_patch.esp" }, "regex": "spirithawke edit", "is_negated": true @@ -6567,9 +6557,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "dw_assassination.esp" - } + "item": "dw_assassination.esp" }, "regex": "bite works only with vampire embrace", "is_negated": true @@ -6595,12 +6583,10 @@ "expression_b": { "VER": { "expression": { - "Atomic": { - "item": "vampire_embrace.esp" - } + "item": "vampire_embrace.esp" }, "operator": "Greater", - "version": "2.3" + "version": "2.3.0" } }, "plugins": [] @@ -6613,9 +6599,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "dw_assassination.esp" - } + "item": "dw_assassination.esp" }, "regex": "bite works only with vampire embrace", "is_negated": false @@ -9337,12 +9321,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "balmora university v.esp" - } + "item": "balmora university v.esp" }, "operator": "Less", - "version": "2.3" + "version": "2.3.0" } } ], @@ -11066,9 +11048,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "complete armor joints.esp" - } + "item": "complete armor joints.esp" }, "regex": "better morrowind armor", "is_negated": true @@ -11093,9 +11073,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "lefemmarmor.esp" - } + "item": "lefemmarmor.esp" }, "regex": "lefemm(tm) armor", "is_negated": false @@ -11125,9 +11103,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "snow prince armor redux.esp" - } + "item": "snow prince armor redux.esp" }, "regex": "a model replacer for the ancient steel armor", "is_negated": true @@ -11894,9 +11870,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "ufr_v3dot2.esp" - } + "item": "ufr_v3dot2.esp" }, "regex": "this version is compatible with better robes and better clothes.", "is_negated": true @@ -11925,9 +11899,7 @@ "expression_a": { "DESC": { "expression": { - "Atomic": { - "item": "ufr_v3dot2.esp" - } + "item": "ufr_v3dot2.esp" }, "regex": "this version is compatible with better robes and better clothes.", "is_negated": false @@ -12029,9 +12001,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "ufr_v3dot2.esp" - } + "item": "ufr_v3dot2.esp" }, "regex": "this version is compatible with better robes and better clothes.", "is_negated": false @@ -12934,12 +12904,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "blood and gore.esp" - } + "item": "blood and gore.esp" }, "operator": "Less", - "version": "2.14" + "version": "2.14.0" } } ], @@ -12959,20 +12927,16 @@ { "VER": { "expression": { - "Atomic": { - "item": "blood and gore.esp" - } + "item": "blood and gore.esp" }, "operator": "Equal", - "version": "2.14" + "version": "2.14.0" } }, { "DESC": { "expression": { - "Atomic": { - "item": "blood and gore.esp" - } + "item": "blood and gore.esp" }, "regex": "dragon32", "is_negated": true @@ -15635,9 +15599,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - equipment.esp" - } + "item": "btb - equipment.esp" }, "regex": "tooplex", "is_negated": true @@ -15695,9 +15657,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - equipment.esp" - } + "item": "btb - equipment.esp" }, "regex": "tooplex", "is_negated": false @@ -15749,9 +15709,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - equipment.esp" - } + "item": "btb - equipment.esp" }, "regex": "left gloves addon", "is_negated": true @@ -15773,9 +15731,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - equipment.esp" - } + "item": "btb - equipment.esp" }, "regex": "left gloves addon", "is_negated": false @@ -15797,9 +15753,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - equipment.esp" - } + "item": "btb - equipment.esp" }, "regex": "tooplex", "is_negated": false @@ -15808,9 +15762,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "better clothes complete (btb edit).esp" - } + "item": "better clothes complete (btb edit).esp" }, "regex": "tooplex", "is_negated": true @@ -15827,9 +15779,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sirluthor-tools.esp" - } + "item": "sirluthor-tools.esp" }, "regex": "tooplex", "is_negated": true @@ -15862,9 +15812,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": true @@ -15886,9 +15834,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -15915,9 +15861,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -15934,9 +15878,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "syc_athomealchemy.esp" - } + "item": "syc_athomealchemy.esp" }, "regex": "tooplex", "is_negated": true @@ -15958,9 +15900,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - alchemy.esp" - } + "item": "btb - alchemy.esp" }, "regex": "tooplex", "is_negated": true @@ -15982,9 +15922,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - alchemy.esp" - } + "item": "btb - alchemy.esp" }, "regex": "tooplex", "is_negated": false @@ -16006,9 +15944,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - alchemy.esp" - } + "item": "btb - alchemy.esp" }, "regex": "tooplex", "is_negated": false @@ -16988,9 +16924,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "changing faces replacer.esp" - } + "item": "changing faces replacer.esp" }, "regex": "changes all faces", "is_negated": false @@ -17762,12 +17696,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "class abilities .esp" - } + "item": "class abilities .esp" }, "operator": "Less", - "version": "3.1" + "version": "3.1.0" } } ], @@ -17781,12 +17713,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "class abilities .esp" - } + "item": "class abilities .esp" }, "operator": "Less", - "version": "3.1" + "version": "3.1.0" } }, { @@ -18267,12 +18197,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "1gr_comp_beryl.esp" - } + "item": "1gr_comp_beryl.esp" }, "operator": "Greater", - "version": "1.0" + "version": "1.0.0" } }, { @@ -21451,9 +21379,7 @@ { "VER": { "expression": { - "Atomic": { - "item": "decorator+ (balanced).esp" - } + "item": "decorator+ (balanced).esp" }, "operator": "Less", "version": "1.1.1" @@ -21462,9 +21388,7 @@ { "VER": { "expression": { - "Atomic": { - "item": "decorator+.esp" - } + "item": "decorator+.esp" }, "operator": "Less", "version": "1.1.1" @@ -21848,9 +21772,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "vampire_embrace.esp" - } + "item": "vampire_embrace.esp" }, "regex": "vampire embrace 2.4", "is_negated": false @@ -21871,9 +21793,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -21902,9 +21822,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "vampire_embrace.esp" - } + "item": "vampire_embrace.esp" }, "regex": "vampire embrace 2.4", "is_negated": false @@ -21913,12 +21831,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "vampiric hunger base.esp" - } + "item": "vampiric hunger base.esp" }, "operator": "Greater", - "version": "1.0" + "version": "1.0.0" } } ] @@ -21944,12 +21860,10 @@ "expression": { "VER": { "expression": { - "Atomic": { - "item": "scentofblood_vr.esp" - } + "item": "scentofblood_vr.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } } } @@ -24072,12 +23986,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "dwemer shock gun tribute.esp" - } + "item": "dwemer shock gun tribute.esp" }, "operator": "Less", - "version": "2.3" + "version": "2.3.0" } } ], @@ -24612,12 +24524,10 @@ "expression_a": { "VER": { "expression": { - "Atomic": { - "item": "elders mca add-on v2.0.esp" - } + "item": "elders mca add-on v2.0.esp" }, "operator": "Equal", - "version": "2.0" + "version": "2.0.0" } }, "expression_b": { @@ -24675,12 +24585,10 @@ "expression_a": { "VER": { "expression": { - "Atomic": { - "item": "elders mca add-on v2.0.esp" - } + "item": "elders mca add-on v2.0.esp" }, "operator": "Equal", - "version": "2.02" + "version": "2.2.0" } }, "expression_b": { @@ -24720,9 +24628,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -24753,12 +24659,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "elders mca add-on v2.0.esp" - } + "item": "elders mca add-on v2.0.esp" }, "operator": "Equal", - "version": "2.02" + "version": "2.2.0" } } ] @@ -24827,9 +24731,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -25157,9 +25059,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "turenyalredone.esp" - } + "item": "turenyalredone.esp" }, "regex": "redoes turenyal to be better than things.", "is_negated": true @@ -26433,9 +26333,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -27730,12 +27628,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "fletcher.esp" - } + "item": "fletcher.esp" }, "operator": "Less", - "version": "2.0" + "version": "2.0.0" } } ], @@ -28104,9 +28000,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "ships of the imperial navy.esp" - } + "item": "ships of the imperial navy.esp" }, "regex": "solstheim", "is_negated": true @@ -32770,12 +32664,10 @@ "expression_b": { "VER": { "expression": { - "Atomic": { - "item": "havish.esm" - } + "item": "havish.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, "plugins": [] @@ -32817,12 +32709,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "havish mini patch.esp" - } + "item": "havish mini patch.esp" }, "operator": "Less", - "version": "2.0" + "version": "2.0.0" } } ], @@ -32846,34 +32736,28 @@ { "VER": { "expression": { - "Atomic": { - "item": "havish.esm" - } + "item": "havish.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "havishm.esm" - } + "item": "havishm.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "havishmn.esm" - } + "item": "havishmn.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } } ] @@ -32935,34 +32819,28 @@ { "VER": { "expression": { - "Atomic": { - "item": "havish.esm" - } + "item": "havish.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "havishm.esm" - } + "item": "havishm.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "havishmn.esm" - } + "item": "havishmn.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } } ] @@ -35012,9 +34890,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8.1", "is_negated": false @@ -35202,9 +35078,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "better morrowind armor defemm(a).esp" - } + "item": "better morrowind armor defemm(a).esp" }, "regex": "hollaajith", "is_negated": true @@ -35213,9 +35087,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "better morrowind armor defemm(o).esp" - } + "item": "better morrowind armor defemm(o).esp" }, "regex": "hollaajith", "is_negated": true @@ -35224,9 +35096,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "better morrowind armor defemm(r).esp" - } + "item": "better morrowind armor defemm(r).esp" }, "regex": "hollaajith", "is_negated": true @@ -35235,9 +35105,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "better morrowind armor.esp" - } + "item": "better morrowind armor.esp" }, "regex": "hollaajith", "is_negated": true @@ -35249,9 +35117,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "complete armor joints.esp" - } + "item": "complete armor joints.esp" }, "regex": "hollaajith", "is_negated": true @@ -35260,9 +35126,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "complete armor joints.esp" - } + "item": "complete armor joints.esp" }, "regex": "better morrowind armor", "is_negated": false @@ -35277,9 +35141,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "snow prince armor redux.esp" - } + "item": "snow prince armor redux.esp" }, "regex": "hollaajith", "is_negated": true @@ -35288,9 +35150,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "snow prince armor redux.esp" - } + "item": "snow prince armor redux.esp" }, "regex": "a model replacer for the ancient steel armor", "is_negated": false @@ -35332,9 +35192,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "complete armor joints.esp" - } + "item": "complete armor joints.esp" }, "regex": "hollaajith", "is_negated": true @@ -35343,9 +35201,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "complete armor joints.esp" - } + "item": "complete armor joints.esp" }, "regex": "better morrowind armor", "is_negated": true @@ -35384,9 +35240,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "snow prince armor redux.esp" - } + "item": "snow prince armor redux.esp" }, "regex": "hollaajith", "is_negated": true @@ -35395,9 +35249,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "snow prince armor redux.esp" - } + "item": "snow prince armor redux.esp" }, "regex": "a model replacer for the ancient steel armor", "is_negated": true @@ -37724,23 +37576,19 @@ { "VER": { "expression": { - "Atomic": { - "item": "1gr_comp_beryl.esp" - } + "item": "1gr_comp_beryl.esp" }, "operator": "Less", - "version": "1.1" + "version": "1.1.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "constance1_0.esp" - } + "item": "constance1_0.esp" }, "operator": "Less", - "version": "1.1" + "version": "1.1.0" } } ] @@ -39337,9 +39185,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -41249,9 +41095,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -41260,9 +41104,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -41284,12 +41126,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "lgnpc_sn.esp" - } + "item": "lgnpc_sn.esp" }, "operator": "Less", - "version": "0.32" + "version": "0.32.0" } }, { @@ -43009,12 +42849,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "npc lcv schedules 03.esp" - } + "item": "npc lcv schedules 03.esp" }, "operator": "Greater", - "version": "03-12" + "version": "3.0.0-12" } }, { @@ -44354,9 +44192,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -44721,9 +44557,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -45813,9 +45647,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mqe_mainquestenhancers.esp" - } + "item": "mqe_mainquestenhancers.esp" }, "regex": "abot", "is_negated": true @@ -47355,9 +47187,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mills of morrowind.esp" - } + "item": "mills of morrowind.esp" }, "regex": "abot", "is_negated": true @@ -47373,9 +47203,7 @@ "expression_a": { "DESC": { "expression": { - "Atomic": { - "item": "mills of morrowind.esp" - } + "item": "mills of morrowind.esp" }, "regex": "abot", "is_negated": true @@ -48266,9 +48094,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -50194,9 +50020,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1200 types of npcs to over 550", "is_negated": true @@ -50205,12 +50029,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "operator": "Less", - "version": "8.2" + "version": "8.2.0" } } ] @@ -50581,9 +50403,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -50592,9 +50412,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50603,9 +50421,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8", "is_negated": false @@ -50649,9 +50465,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50660,9 +50474,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8", "is_negated": false @@ -50690,9 +50502,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50701,9 +50511,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8", "is_negated": false @@ -50729,9 +50537,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -50740,9 +50546,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50777,9 +50581,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -50788,9 +50590,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50857,9 +50657,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -50868,9 +50666,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -50879,9 +50675,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -50890,9 +50684,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -50972,9 +50764,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8", "is_negated": true @@ -50983,9 +50773,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca - tr addon.esp" - } + "item": "mca - tr addon.esp" }, "regex": "7.1a", "is_negated": true @@ -51012,9 +50800,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51023,9 +50809,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51034,9 +50818,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -51169,9 +50951,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51183,9 +50963,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51298,9 +51076,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51309,9 +51085,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51348,9 +51122,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51359,9 +51131,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51474,9 +51244,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51501,9 +51269,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1000 types of npcs to over 400", "is_negated": false @@ -51512,9 +51278,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -51625,9 +51389,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51713,9 +51475,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1000 friendly", "is_negated": false @@ -51724,9 +51484,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51789,9 +51547,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51851,9 +51607,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -51894,9 +51648,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52057,9 +51809,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52079,9 +51829,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52101,9 +51849,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -52126,9 +51872,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "adds 1100 types of npcs to over 550", "is_negated": false @@ -52156,9 +51900,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8.1", "is_negated": false @@ -52181,9 +51923,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8.1", "is_negated": false @@ -52268,9 +52008,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52290,9 +52028,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52343,9 +52079,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "v8.1", "is_negated": false @@ -52497,9 +52231,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52783,9 +52515,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -52924,9 +52654,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -53055,9 +52783,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -62327,9 +62053,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "lefemmarmor.esp" - } + "item": "lefemmarmor.esp" }, "regex": "lefemm(tm) armor", "is_negated": false @@ -64131,9 +63855,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "pssorticon.esp" - } + "item": "pssorticon.esp" }, "regex": "spirithawke", "is_negated": true @@ -64824,9 +64546,7 @@ { "VER": { "expression": { - "Atomic": { - "item": "pursuit enhanced.esp" - } + "item": "pursuit enhanced.esp" }, "operator": "Equal", "version": "1.2.4" @@ -65323,12 +65043,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "encumbrance bar.esp" - } + "item": "encumbrance bar.esp" }, "operator": "Less", - "version": "0.3" + "version": "0.3.0" } } ], @@ -66324,9 +66042,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -67813,23 +67529,19 @@ { "VER": { "expression": { - "Atomic": { - "item": "lgnpc_paxredoran*.esp" - } + "item": "lgnpc_paxredoran*.esp" }, "operator": "Less", - "version": "1.21" + "version": "1.21.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "lgnpc_teluvirith_v.esp" - } + "item": "lgnpc_teluvirith_v.esp" }, "operator": "Less", - "version": "1.20" + "version": "1.20.0" } } ] @@ -68018,12 +67730,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "rise of house telvanni.esm" - } + "item": "rise of house telvanni.esm" }, "operator": "Equal", - "version": "1.3" + "version": "1.3.0" } }, { @@ -69320,56 +69030,46 @@ { "VER": { "expression": { - "Atomic": { - "item": "scentofblood.esp" - } + "item": "scentofblood.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "scentofblood_mca.esp" - } + "item": "scentofblood_mca.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "scentofblood_mca_vr.esp" - } + "item": "scentofblood_mca_vr.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "scentofblood_mwse_addon.esp" - } + "item": "scentofblood_mwse_addon.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "scentofblood_vr.esp" - } + "item": "scentofblood_vr.esp" }, "operator": "Less", - "version": "1.2" + "version": "1.2.0" } }, { @@ -69683,9 +69383,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -70841,9 +70539,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate.esp" - } + "item": "sg1-sgaileach_estate.esp" }, "regex": "version 2.01", "is_negated": true @@ -70852,9 +70548,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate-nom.esp" - } + "item": "sg1-sgaileach_estate-nom.esp" }, "regex": "version 2.01", "is_negated": true @@ -70880,12 +70574,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -70912,9 +70604,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate.esp" - } + "item": "sg1-sgaileach_estate.esp" }, "regex": "version 2.01", "is_negated": false @@ -70923,9 +70613,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate-nom.esp" - } + "item": "sg1-sgaileach_estate-nom.esp" }, "regex": "version 2.01", "is_negated": false @@ -70958,9 +70646,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate.esp" - } + "item": "sg1-sgaileach_estate.esp" }, "regex": "version 2.01", "is_negated": false @@ -70969,9 +70655,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate-nom.esp" - } + "item": "sg1-sgaileach_estate-nom.esp" }, "regex": "version 2.01", "is_negated": false @@ -70986,12 +70670,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -71045,9 +70727,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate.esp" - } + "item": "sg1-sgaileach_estate.esp" }, "regex": "version 2.01", "is_negated": false @@ -71056,9 +70736,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sg1-sgaileach_estate-nom.esp" - } + "item": "sg1-sgaileach_estate-nom.esp" }, "regex": "version 2.01", "is_negated": false @@ -73448,9 +73126,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -73854,9 +73530,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "btb - spells.esp" - } + "item": "btb - spells.esp" }, "regex": "tooplex", "is_negated": false @@ -74105,12 +73779,10 @@ "expression_a": { "VER": { "expression": { - "Atomic": { - "item": "sris_alchemy_bm.esp" - } + "item": "sris_alchemy_bm.esp" }, "operator": "Equal", - "version": "1.1109" + "version": "1.1109.0" } }, "expression_b": { @@ -74152,9 +73824,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "sris_alchemy_bm.esp" - } + "item": "sris_alchemy_bm.esp" }, "regex": "adelie", "is_negated": false @@ -74698,9 +74368,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "starfires npc additions ver-1.11.esp" - } + "item": "starfires npc additions ver-1.11.esp" }, "regex": "redesigned vivec compatible version", "is_negated": true @@ -75137,12 +74805,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } } ], @@ -75159,12 +74825,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } }, { @@ -75198,12 +74862,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } }, { @@ -75224,12 +74886,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } }, { @@ -75259,12 +74919,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } }, { @@ -75309,12 +74967,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Less", - "version": "2" + "version": "2.0.0" } }, { @@ -75379,12 +75035,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -75467,12 +75121,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -75540,12 +75192,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -75790,12 +75440,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "stinkers.esp" - } + "item": "stinkers.esp" }, "operator": "Greater", - "version": "1.71" + "version": "1.71.0" } }, { @@ -76365,9 +76013,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "lefemmarmor.esp" - } + "item": "lefemmarmor.esp" }, "regex": "lefemm(tm) armor", "is_negated": false @@ -77204,9 +76850,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "lefemmarmor.esp" - } + "item": "lefemmarmor.esp" }, "regex": "lefemm(tm) armor", "is_negated": false @@ -77576,9 +77220,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "lefemmarmor.esp" - } + "item": "lefemmarmor.esp" }, "regex": "lefemm(tm) armor", "is_negated": false @@ -79607,9 +79249,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -79684,12 +79324,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "texture fix .esm" - } + "item": "texture fix .esm" }, "operator": "Less", - "version": "2.0" + "version": "2.0.0" } } ], @@ -79933,12 +79571,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "constance1_0.esp" - } + "item": "constance1_0.esp" }, "operator": "Less", - "version": "1.1" + "version": "1.1.0" } }, { @@ -84222,9 +83858,7 @@ "expression_b": { "DESC": { "expression": { - "Atomic": { - "item": "mca.esm" - } + "item": "mca.esm" }, "regex": "over 1200 friendly", "is_negated": false @@ -84399,12 +84033,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "uvirith's legacy_.esp" - } + "item": "uvirith's legacy_.esp" }, "operator": "Greater", - "version": "3.1" + "version": "3.1.0" } }, { @@ -84460,12 +84092,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "uvirith's legacy_.esp" - } + "item": "uvirith's legacy_.esp" }, "operator": "Greater", - "version": "3.2" + "version": "3.2.0" } } ], @@ -84609,12 +84239,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "rise of house telvanni.esm" - } + "item": "rise of house telvanni.esm" }, "operator": "Equal", - "version": "1.52" + "version": "1.52.0" } } ] @@ -86020,12 +85648,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "vampiric hunger base.esp" - } + "item": "vampiric hunger base.esp" }, "operator": "Equal", - "version": "1.2" + "version": "1.2.0" } }, { @@ -86317,31 +85943,25 @@ { "VER": { "expression": { - "Atomic": { - "item": "vampiric hunger base.esp" - } + "item": "vampiric hunger base.esp" }, "operator": "Greater", - "version": "1.0" + "version": "1.0.0" } }, { "VER": { "expression": { - "Atomic": { - "item": "scripted_spells.esp" - } + "item": "scripted_spells.esp" }, "operator": "Greater", - "version": "1.4" + "version": "1.4.0" } }, { "DESC": { "expression": { - "Atomic": { - "item": "vampire_embrace.esp" - } + "item": "vampire_embrace.esp" }, "regex": "vampire embrace 2.4", "is_negated": false @@ -86501,9 +86121,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "vampire_embrace.esp" - } + "item": "vampire_embrace.esp" }, "regex": "vampire embrace 2.4", "is_negated": false @@ -87779,9 +87397,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "vivec expansion 2.0.esp" - } + "item": "vivec expansion 2.0.esp" }, "regex": "fixed the floating door", "is_negated": true @@ -88958,12 +88574,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "abotwaterlife.esm" - } + "item": "abotwaterlife.esm" }, "operator": "Greater", - "version": "1.12" + "version": "1.12.0" } }, { @@ -88982,12 +88596,10 @@ { "VER": { "expression": { - "Atomic": { - "item": "abotwaterlife.esm" - } + "item": "abotwaterlife.esm" }, "operator": "Greater", - "version": "1.16" + "version": "1.16.0" } }, { @@ -91249,9 +90861,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "windows glow - raven rock eng.esp" - } + "item": "windows glow - raven rock eng.esp" }, "regex": "abot", "is_negated": true diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 15d522a..841aada 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -19,6 +19,8 @@ eframe = { version = "0.26.0", default-features = false, features = [ log = { workspace = true } serde = { workspace = true } +byteorder = { workspace = true } + simplelog = "0.12" toml = "0.8" diff --git a/src/expressions.rs b/src/expressions.rs index cb61a40..cf2c7d9 100644 --- a/src/expressions.rs +++ b/src/expressions.rs @@ -4,9 +4,10 @@ use std::fmt::Display; +use semver::VersionReq; use serde::{Deserialize, Serialize}; -use crate::{wild_contains, PluginData}; +use crate::{wild_contains, wild_contains_data, PluginData}; // An expression may be evaluated against a load order pub trait TExpression { @@ -283,22 +284,49 @@ impl Display for NOT { /// [DESC /regex/ A.esp] or [DESC !/regex/ A.esp] #[derive(Debug, Serialize, Deserialize)] pub struct DESC { - pub expression: Box, + pub expression: Atomic, pub regex: String, pub is_negated: bool, } impl DESC { - pub fn new(expression: Expression, description: String, is_negated: bool) -> Self { + pub fn new(expression: Atomic, regex: String, is_negated: bool) -> Self { Self { - expression: Box::new(expression), - regex: description, + expression, + regex, is_negated, } } } impl TExpression for DESC { fn eval(&self, items: &[PluginData]) -> Option> { - self.expression.eval(items) + // check the version + if let Some(plugins) = wild_contains_data(items, &self.expression.item) { + let mut results = vec![]; + for p in &plugins { + if let Some(description) = &p.description { + if let Ok(pattern) = regex::Regex::new(&self.regex) { + match self.is_negated { + true => { + if !pattern.is_match(description) { + results.push(p.name.clone()); + } + } + false => { + if pattern.is_match(description) { + results.push(p.name.clone()); + } + } + } + } + } + } + if results.is_empty() { + return None; + } else { + return Some(results); + } + } + None } } impl Clone for DESC { @@ -324,7 +352,7 @@ impl Display for DESC { //////////////////////////////////////////////////////////////////////// // SIZE -/// TODO The Size predicate is a special predicate that matches the filesize of the plugin +/// The Size predicate is a special predicate that matches the filesize of the plugin /// [SIZE ### A.esp] or [SIZE !### A.esp] #[derive(Debug, Serialize, Deserialize)] pub struct SIZE { @@ -343,11 +371,8 @@ impl SIZE { } impl TExpression for SIZE { fn eval(&self, items: &[PluginData]) -> Option> { - // check the atomic - let results = crate::wild_contains_data(items, &self.expression.item); - // check the size - if let Some(plugins) = results { + if let Some(plugins) = wild_contains_data(items, &self.expression.item) { let mut results = vec![]; for p in &plugins { if self.is_negated { @@ -407,20 +432,20 @@ impl Display for EVerOperator { } } -/// TODO The Ver predicate is a special predicate that first tries to match the version number string stored in the plugin header, +/// The Ver predicate is a special predicate that first tries to match the version number string stored in the plugin header, /// and if that fails it tries to match the version number from the plugin filename. /// If a version number is found, it can be used in a comparison. /// Syntax: [VER operator version plugin.esp] #[derive(Debug, Serialize, Deserialize)] pub struct VER { - pub expression: Box, + pub expression: Atomic, pub operator: EVerOperator, pub version: String, } impl VER { - pub fn new(expression: Expression, operator: EVerOperator, version: String) -> Self { + pub fn new(expression: Atomic, operator: EVerOperator, version: String) -> Self { Self { - expression: Box::new(expression), + expression, operator, version, } @@ -428,7 +453,43 @@ impl VER { } impl TExpression for VER { fn eval(&self, items: &[PluginData]) -> Option> { - self.expression.eval(items) + // check the version + if let Some(plugins) = wild_contains_data(items, &self.expression.item) { + let mut results = vec![]; + for p in &plugins { + if let Some(plugin_version) = &p.version { + // we can unwrap here because we know the version is valid + let semversion = semver::Version::parse(&self.version).unwrap(); + let matches = match self.operator { + EVerOperator::Less => { + let req = + VersionReq::parse(format!("<{}", semversion).as_str()).unwrap(); + req.matches(plugin_version) + } + EVerOperator::Equal => { + let req = + VersionReq::parse(format!("={}", semversion).as_str()).unwrap(); + req.matches(plugin_version) + } + EVerOperator::Greater => { + let req = + VersionReq::parse(format!(">{}", semversion).as_str()).unwrap(); + req.matches(plugin_version) + } + }; + + if matches { + results.push(p.name.clone()); + } + } + } + if results.is_empty() { + return None; + } else { + return Some(results); + } + } + None } } impl Clone for VER { diff --git a/src/lib.rs b/src/lib.rs index 1367f35..837c09c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::error::Error; use std::fs::{self, File}; -use std::io::{self, BufRead, Write}; +use std::io::{self, BufRead, Read, Seek, Write}; use std::ops::ControlFlow; use std::path::{Path, PathBuf}; use std::{env, vec}; @@ -13,6 +13,7 @@ pub mod parser; pub mod rules; pub mod sorter; +use byteorder::{LittleEndian, ReadBytesExt}; use filetime::set_file_mtime; use ini::Ini; use log::{error, info, warn}; @@ -205,13 +206,13 @@ fn download_plox_rules(rules_dir: &PathBuf) { } } -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct PluginData { pub name: String, pub size: u64, pub description: Option, - pub version: Option, + pub version: Option, } impl PluginData { @@ -302,13 +303,32 @@ where .iter() .filter_map(|f| { if let Some(file_name) = f.file_name().and_then(|n| n.to_str()) { - // TODO TES3 parse the file and get the header content - let data = PluginData { + let mut data = PluginData { name: file_name.to_owned(), size: f.metadata().unwrap().len(), description: None, version: None, }; + match parse_header(f) { + Ok(header) => { + data.description = Some(header.description); + + // parse semver + let version = header.version.to_string(); + match lenient_semver::parse(&version) { + Ok(v) => { + data.version = Some(v); + } + Err(e) => { + log::debug!("Error parsing version: {}", e); + } + } + } + Err(e) => { + log::debug!("Error parsing header: {}", e); + } + } + return Some(data); } None @@ -365,13 +385,31 @@ where .iter() .filter_map(|f| { if let Some(file_name) = f.file_name().and_then(|n| n.to_str()) { - // TODO TES3 parse the file and get the header content - let data = PluginData { + let mut data = PluginData { name: file_name.to_owned(), size: f.metadata().unwrap().len(), description: None, version: None, }; + match parse_header(f) { + Ok(header) => { + data.description = Some(header.description); + + // parse semver + let version = header.version.to_string(); + match lenient_semver::parse(&version) { + Ok(v) => { + data.version = Some(v); + } + Err(e) => { + log::debug!("Error parsing version: {}", e); + } + } + } + Err(e) => { + log::debug!("Error parsing header: {}", e); + } + } return Some(data); } None @@ -513,6 +551,154 @@ fn update_tes3(result: &[String]) -> std::io::Result<()> { Ok(()) } +/// Checks if the list of mods is in the correct order +pub fn check_order(result: &[String], order_rules: &[EOrderRule]) -> bool { + let order = get_ordering_from_order_rules(order_rules); + let pairs = order; + for (a, b) in pairs { + if let Some(results_for_a) = wild_contains(result, &a) { + if let Some(results_for_b) = wild_contains(result, &b) { + for i in &results_for_a { + for j in &results_for_b { + let pos_a = result.iter().position(|x| x == i).unwrap(); + let pos_b = result.iter().position(|x| x == j).unwrap(); + if pos_a > pos_b { + return false; + } + } + } + } + } + } + + true +} + +//////////////////////////////////////////////////////////////////////// +/// TES3 +//////////////////////////////////////////////////////////////////////// +#[derive(Debug, Clone, Default)] +pub struct Tes3Header { + pub version: f32, + pub description: String, + pub masters: Option>, +} + +pub fn parse_header(f: &Path) -> std::io::Result { + let magic: u32 = 861095252; + // read file to binary reader + let mut reader = std::io::BufReader::new(std::fs::File::open(f)?); + // read first 4 bytes and check magic + let file_magic = reader.read_u32::()?; + if file_magic != magic { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Not a valid TES3 plugin", + )); + } + + // next 4 bytes is the size of the header + let header_size = reader.read_u32::()?; + // skip 8 bytes + reader.seek(std::io::SeekFrom::Current(8))?; + // read the header + let mut header_buffer = vec![0; header_size as usize]; + reader.read_exact(&mut header_buffer)?; + + let mut reader = std::io::Cursor::new(header_buffer); + let header = parse_hedr(&mut reader, header_size as u64)?; + Ok(header) +} + +fn parse_hedr(reader: &mut R, stream_size: u64) -> std::io::Result { + let magic: u32 = 1380205896; + // check magic + let file_magic = reader.read_u32::()?; + + if file_magic != magic { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Not a valid TES3 plugin", + )); + } + + let mut header = Tes3Header::default(); + + // next 4 bytes is the size of the header + let _header_size = reader.read_u32::()?; + + // next 4 bytes is the version + header.version = reader.read_f32::()?; + + // next 4 bytes is unused + let _ = reader.read_u32::()?; + + // read 32 bytes as string + let mut string_buffer = [0; 32]; + reader.read_exact(&mut string_buffer)?; + let _author = String::from_utf8_lossy(&string_buffer).to_string(); + + // read 256 bytes as string + let mut string_buffer = [0; 256]; + reader.read_exact(&mut string_buffer)?; + header.description = String::from_utf8_lossy(&string_buffer) + .trim_end_matches('\0') + .to_string(); + + // read 4 bytes as u32 + let _num_records = reader.read_u32::()?; + + let master_magic: u32 = 1414742349; + let data_magic: u32 = 1096040772; + + // read masters + let mut masters = vec![]; + loop { + let magic = reader.read_u32::()?; + if magic == master_magic { + // next 4 bytes is the size of the master string name + let master_size = reader.read_u32::()?; + // read master name + let mut master_buffer = vec![0; master_size as usize]; + reader.read_exact(&mut master_buffer)?; + let master_name = String::from_utf8_lossy(&master_buffer).to_string(); + + // read data magic + let magic_data = reader.read_u32::()?; + if magic_data != data_magic { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Not a valid TES3 plugin", + )); + } + // next 4 bytes is the size of the master data + let master_data_size = reader.read_u32::()?; + // verify master data size is 8 + if master_data_size != 8 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Not a valid TES3 plugin", + )); + } + + // next 8 bytes is size + let size = reader.read_u64::()?; + + masters.push((master_name.trim_end_matches('\0').to_string(), size)); + + // break out if end of stream + if reader.stream_position()? >= stream_size { + break; + } + } else { + break; + } + } + header.masters = Some(masters); + + Ok(header) +} + fn redate_mods(files: &[PathBuf]) -> Result<(), io::Error> { let fixed_file_times: HashMap = HashMap::from([ ("morrowind.esm".into(), 1024695106), @@ -542,29 +728,6 @@ fn redate_mods(files: &[PathBuf]) -> Result<(), io::Error> { Ok(()) } -/// Checks if the list of mods is in the correct order -pub fn check_order(result: &[String], order_rules: &[EOrderRule]) -> bool { - let order = get_ordering_from_order_rules(order_rules); - let pairs = order; - for (a, b) in pairs { - if let Some(results_for_a) = wild_contains(result, &a) { - if let Some(results_for_b) = wild_contains(result, &b) { - for i in &results_for_a { - for j in &results_for_b { - let pos_a = result.iter().position(|x| x == i).unwrap(); - let pos_b = result.iter().position(|x| x == j).unwrap(); - if pos_a > pos_b { - return false; - } - } - } - } - } - } - - true -} - //////////////////////////////////////////////////////////////////////// /// HELPERS //////////////////////////////////////////////////////////////////////// diff --git a/src/parser.rs b/src/parser.rs index e5956e8..d186535 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -645,10 +645,24 @@ impl Parser { if let Some((expr, regex, negated)) = parse_desc(body) { // do something let expressions = self.parse_expressions(expr.as_bytes())?; - if let Some(first) = expressions.into_iter().last() { - let expr = DESC::new(first, regex, negated); + // check that it is of len 1 + if expressions.len() != 1 { + return Err(Error::new( + ErrorKind::Other, + "Parsing error: DESC expression must have exactly one child expression", + )); + } + + // check that the child expression is an atomic + if let Some(Expression::Atomic(atomic)) = expressions.first() { + let expr = DESC::new(atomic.clone(), regex, negated); return Ok(expr.into()); } + + return Err(Error::new( + ErrorKind::Other, + "Parsing error: DESC expression must have an atomic child expression", + )); } Err(Error::new( ErrorKind::Other, @@ -686,10 +700,24 @@ impl Parser { if let Some((expr, operator, version)) = parse_ver(body) { // do something let expressions = self.parse_expressions(expr.as_bytes())?; - if let Some(first) = expressions.into_iter().last() { - let expr = VER::new(first, operator, version); + // check that it is of len 1 + if expressions.len() != 1 { + return Err(Error::new( + ErrorKind::Other, + "Parsing error: VER expression must have exactly one child expression", + )); + } + + // check that the child expression is an atomic + if let Some(Expression::Atomic(atomic)) = expressions.first() { + let expr = VER::new(atomic.clone(), operator, version.to_string()); return Ok(expr.into()); } + + return Err(Error::new( + ErrorKind::Other, + "Parsing error: VER expression must have an atomic child expression", + )); } Err(Error::new( ErrorKind::Other, @@ -771,38 +799,32 @@ fn parse_size(input: &str) -> Option<(String, u64, bool)> { } /// Parses the VER predicate and returns its parts -fn parse_ver(input: &str) -> Option<(String, EVerOperator, String)> { +fn parse_ver(input: &str) -> Option<(String, EVerOperator, semver::Version)> { // >1.51 Rise of House Telvanni.esm // = 2.14 Blood and Gore.esp // < 3.1 Class Abilities .esp if let Some(input) = input.strip_prefix('<') { if let Some(version) = input.split_whitespace().next() { if let Some(right_part) = input.trim_start().strip_prefix(version) { - return Some(( - right_part.to_owned(), - EVerOperator::Less, - version.to_owned(), - )); + if let Ok(semversion) = lenient_semver::parse(version) { + return Some((right_part.to_owned(), EVerOperator::Less, semversion)); + } } } } else if let Some(input) = input.strip_prefix('>') { if let Some(version) = input.split_whitespace().next() { if let Some(right_part) = input.trim_start().strip_prefix(version) { - return Some(( - right_part.to_owned(), - EVerOperator::Greater, - version.to_owned(), - )); + if let Ok(semversion) = lenient_semver::parse(version) { + return Some((right_part.to_owned(), EVerOperator::Greater, semversion)); + } } } } else if let Some(input) = input.strip_prefix('=') { if let Some(version) = input.split_whitespace().next() { if let Some(right_part) = input.trim_start().strip_prefix(version) { - return Some(( - right_part.to_owned(), - EVerOperator::Equal, - version.to_owned(), - )); + if let Ok(semversion) = lenient_semver::parse(version) { + return Some((right_part.to_owned(), EVerOperator::Equal, semversion)); + } } } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 1dac9b4..f8cbd35 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod integration_tests { + use std::path::PathBuf; use std::{fs::create_dir_all, io::Write}; use log::warn; @@ -404,4 +405,24 @@ mod integration_tests { ] ) } + + #[test] + fn test_parse_header() { + init(); + + let plugin_test_path = PathBuf::from("tests").join("test1.esp"); + let header = parse_header(&plugin_test_path).expect("failed to parse header"); + + // check some things + assert_eq!(header.version, 1.3_f32); + assert_eq!( + header.description, + "The main data file for BloodMoon.\r\n(requires Morrowind.esm to run)" + ); + // check master files + assert_eq!( + header.masters.unwrap(), + vec![("Morrowind.esm".to_string(), 79837557_u64),] + ); + } } diff --git a/tests/test1.esp b/tests/test1.esp new file mode 100644 index 0000000000000000000000000000000000000000..941f0899284131594b33da84bb2e5b6459da7eff GIT binary patch literal 362 zcmWG>4K|KqWPk$?SC=3iAU`c_nLS9@DYYacwKyeFAviy+q&&YUyBMw%%m~RyRme@u z%u`57EJ;*I%gjktNXsu$aLUQgPw~yq&(q`O(kMzTEX^!REmrW&FDl9}&&*5FOD)b- sD9KkSD$UbGHE*c##I+b0d>w;B_<#;a@g9SVV~8UMP_)$Y5DUmx0NOw-9{>OV literal 0 HcmV?d00001 diff --git a/tests/unit_expression_tests.rs b/tests/unit_expression_tests.rs index e658a50..b190052 100644 --- a/tests/unit_expression_tests.rs +++ b/tests/unit_expression_tests.rs @@ -130,6 +130,187 @@ mod unit_tests { } } + #[test] + fn evaluate_desc() { + init(); + + let mods = [A, B, C, D, E, F] + .iter() + .map(|e| PluginData { + name: e.to_string(), + size: 0_u64, + description: Some("description".to_string()), + version: None, + }) + .collect::>(); + + // [DESC] is true if the plugin description matches the given description + { + let expr = DESC::new(Atomic::from(A), "description".to_string(), false); + assert!(expr.eval(&mods).is_some()); + } + // [DESC] is true if the plugin description matches the given description with regex + { + let expr = DESC::new(Atomic::from(A), "des*".to_string(), false); + assert!(expr.eval(&mods).is_some()); + } + + // [DESC] is false if the plugin description does not match the given description + { + let expr = DESC::new(Atomic::from(A), "another description".to_string(), false); + assert!(expr.eval(&mods).is_none()); + } + + // [DESC] is true if the plugin description does not matches the given description and is negated is true + { + let expr = DESC::new(Atomic::from(A), "another description".to_string(), true); + assert!(expr.eval(&mods).is_some()); + } + + // [DESC] is false if the plugin description does match the given description and is negated is true + { + let expr = DESC::new(Atomic::from(A), "description".to_string(), true); + assert!(expr.eval(&mods).is_none()); + } + } + + #[test] + fn evaluate_ver() { + init(); + + let mods = [A, B, C, D, E, F] + .iter() + .map(|e| PluginData { + name: e.to_string(), + size: 0_u64, + description: None, + version: Some(lenient_semver::parse("1.0").unwrap()), + }) + .collect::>(); + + // Check equals + // [VER] equals is true if the plugin version matches the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Equal, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] equals is false if the plugin version does not matches the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Equal, "1.1.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // Check greater + // [Note this is a newer version, it's broken] [VER > 0.1 foo.esp] + // [VER] greater is true if the plugin version is greater than the rule version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "0.1.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] greater is false if the plugin version is less than the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "1.2.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // [VER] greater is false if the plugin version is equal to the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // Check less + // [Note this is an old version, please upgrade] [VER < 1.2 foo.esp] + // [VER] less is true if the plugin version is less than the rule version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "1.2.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] less is false if the plugin version is greater than the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "0.1.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // [VER] less is false if the plugin version is equal to the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + } + + #[allow(dead_code)] + //TODO add plugin filename version parsing #[test] + fn evaluate_ver_filename() { + init(); + + let mods = ["a.esp", "b.esp"] + .iter() + .map(|e| PluginData { + name: e.to_string(), + size: 0_u64, + description: None, + version: None, + }) + .collect::>(); + + // Check equals + // [VER] equals is true if the plugin version matches the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Equal, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] equals is false if the plugin version does not matches the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Equal, "1.1.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // Check greater + // [Note this is a newer version, it's broken] [VER > 0.1 foo.esp] + // [VER] greater is true if the plugin version is greater than the rule version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "0.1.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] greater is false if the plugin version is less than the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "1.2.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // [VER] greater is false if the plugin version is equal to the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Greater, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // Check less + // [Note this is an old version, please upgrade] [VER < 1.2 foo.esp] + // [VER] less is true if the plugin version is less than the rule version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "1.2.0".to_string()); + assert!(expr.eval(&mods).is_some()); + } + + // [VER] less is false if the plugin version is greater than the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "0.1.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + + // [VER] less is false if the plugin version is equal to the given version + { + let expr = VER::new(Atomic::from(A), EVerOperator::Less, "1.0.0".to_string()); + assert!(expr.eval(&mods).is_none()); + } + } + #[test] fn evaluate_nested() { init(); diff --git a/tests/unit_parser_tests.rs b/tests/unit_parser_tests.rs index 7863bbc..16e4b0e 100644 --- a/tests/unit_parser_tests.rs +++ b/tests/unit_parser_tests.rs @@ -499,7 +499,7 @@ mod unit_tests { .expect("No expressions parsed"); if let Expression::DESC(e) = expr { - assert!(is_atomic(e.expression.as_ref(), expected[1])); + assert!(is_atomic(&e.expression.into(), expected[1])); assert_eq!(format!("/{}/", e.regex), expected[0]); assert!(!e.is_negated); } else { @@ -523,7 +523,7 @@ mod unit_tests { .expect("No expressions parsed"); if let Expression::DESC(e) = expr { - assert!(is_atomic(e.expression.as_ref(), expected[1])); + assert!(is_atomic(&e.expression.into(), expected[1])); assert_eq!(format!("!/{}/", e.regex), expected[0]); assert!(e.is_negated); } else { @@ -618,7 +618,7 @@ mod unit_tests { for (a, b, c) in inputs { test_ver( format!("[VER {a} {b} {c}]").to_lowercase().as_str(), - [a, b, c].to_vec(), + [a, format!("{b}.0").as_str(), c].to_vec(), ); } } @@ -641,7 +641,7 @@ mod unit_tests { if let Expression::VER(e) = expr { assert_eq!(format!("{}", e.operator), expected[0]); assert_eq!(format!("{}", e.version), expected[1]); - assert!(is_atomic(e.expression.as_ref(), expected[2])); + assert!(is_atomic(&e.expression.into(), expected[2])); } else { panic!("wrong type"); } diff --git a/user_rules.json b/user_rules.json index 22151e9..b0bfbb8 100644 --- a/user_rules.json +++ b/user_rules.json @@ -1102,9 +1102,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "mwse_poisoncrafting.esp" - } + "item": "mwse_poisoncrafting.esp" }, "regex": "patches poison crafting", "is_negated": true @@ -3778,9 +3776,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "camonna tong.esp" - } + "item": "camonna tong.esp" }, "regex": "enjoy!", "is_negated": false @@ -3811,9 +3807,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "camonna tong.esp" - } + "item": "camonna tong.esp" }, "regex": "enjoy!", "is_negated": true @@ -8587,9 +8581,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "tr_mainland.esm" - } + "item": "tr_mainland.esm" }, "regex": "main file v. 22.11", "is_negated": false @@ -8633,9 +8625,7 @@ { "DESC": { "expression": { - "Atomic": { - "item": "tr_mainland.esm" - } + "item": "tr_mainland.esm" }, "regex": "main file v. 22.11", "is_negated": false