diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 798c6c7..5794aaa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,28 +1,35 @@ +default_install_hook_types: [pre-commit, commit-msg] repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - args: ['--maxkb=5000'] - - id: check-builtin-literals - - id: check-byte-order-marker + exclude: (?x)^([models/fixtures|docs]) + stages: [pre-commit] - id: check-case-conflict + stages: [pre-commit] - id: check-merge-conflict + stages: [pre-commit] - id: detect-private-key + stages: [pre-commit] - id: forbid-new-submodules + stages: [pre-commit] + - id: check-builtin-literals + stages: [pre-commit] + - id: check-yaml + stages: [pre-commit] - repo: https://github.com/jumanjihouse/pre-commit-hooks rev: 3.0.0 hooks: - - id: git-dirty - id: forbid-binary - exclude: > - (?x)^( - models/fixtures/.*?|docs/.*? - )$ + stages: [pre-commit] + exclude: (?x)^([models/fixtures|docs]) + - id: git-dirty + stages: [pre-commit] - repo: https://github.com/commitizen-tools/commitizen - rev: v3.18.4 + rev: v3.27.0 hooks: - id: commitizen stages: [commit-msg] @@ -30,6 +37,28 @@ repos: - repo: https://github.com/doublify/pre-commit-rust rev: v1.0 hooks: + - id: fmt + stages: [pre-commit] - id: cargo-check + stages: [pre-commit] - id: clippy - - id: fmt + stages: [pre-commit] + +- repo: local + hooks: + - id: cargo-test + stages: [pre-commit] + name: cargo test + description: Run the test suite + entry: cargo test --workspace + language: system + types: [rust] + pass_filenames: false + +- repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + stages: [pre-commit] + args: + - '--ignore-words-list=crate,sav,SAV,ser,extention' diff --git a/Cargo.lock b/Cargo.lock index eaaef09..6a8e157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -53,9 +53,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "binrw" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f36b7cb3ab9ff6a2858650d8dc360e783a5d14dc29594db48c56a3c233cc265" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ea7a8c5c8eeffffac6d54d172444e15beffac6f817fac714460a9a9aa88da3" +dependencies = [ + "either", + "owo-colors", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytemuck" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" + [[package]] name = "cfg-if" version = "1.0.0" @@ -93,7 +135,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] @@ -102,16 +144,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" -[[package]] -name = "cli" -version = "0.1.0" -dependencies = [ - "clap", - "erased-serde", - "models", - "serde_json", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -127,6 +159,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "erased-serde" version = "0.3.31" @@ -136,6 +180,22 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "flate2" version = "1.0.28" @@ -158,6 +218,18 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -171,18 +243,46 @@ dependencies = [ name = "models" version = "0.1.0" dependencies = [ + "binrw", "erased-serde", "flate2", + "pretty_assertions", "serde", "serde_json", + "tempfile", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "post_infinity" version = "0.1.0" dependencies = [ + "binrw", "clap", - "cli", + "erased-serde", + "models", + "serde_json", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", ] [[package]] @@ -203,6 +303,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.17" @@ -226,7 +339,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] @@ -246,6 +359,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.52" @@ -257,6 +381,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -278,15 +415,25 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -295,42 +442,54 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 893f800..bc628ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] + [dependencies] +binrw = "0.14.0" clap = { version = "4.0", features = ["derive"] } -cli = { path = "cli" } +erased-serde = "0.3" +models = { path = "./models" } +serde_json = "1.0" diff --git a/README.md b/README.md index d95319c..1d6513d 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ post_infinity models/fixtures/gate1.spl "extended_headers": [ { "spell_form": 1, - "freindly": 0, + "friendly": 0, "location": 2, "memorised_icon": "SPWI905B", "target_type": 4, @@ -96,3 +96,11 @@ post_infinity models/fixtures/gate1.spl ``` cargo build --release ``` + +## Performance + +It takes about 0.5->1.0s to read all the supported files in an unmodded bg1ee game, into memory, without parsing them. + +```sh +time cargo run -- /chitin.key 0.63s user 1.01s system 105% cpu 1.555 total +``` diff --git a/cli/.gitignore b/cli/.gitignore deleted file mode 100644 index 1e7caa9..0000000 --- a/cli/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -Cargo.lock -target/ diff --git a/cli/Cargo.toml b/cli/Cargo.toml deleted file mode 100644 index d5c2986..0000000 --- a/cli/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "cli" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "4.0", features = ["derive"] } -erased-serde = "0.3" -models = { path = "../models" } -serde_json = "1.0" diff --git a/models/Cargo.toml b/models/Cargo.toml index b0afe04..b8af001 100644 --- a/models/Cargo.toml +++ b/models/Cargo.toml @@ -6,10 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +binrw = "0.14.0" erased-serde = "0.3" +flate2 = { version = "1.0.17" } serde = { version = "1.0.189", features = ["derive"] } serde_json = "1.0.94" -flate2 = { version = "1.0.17" } [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/models/Readme.md b/models/Readme.md index 64103f6..265d9c3 100644 --- a/models/Readme.md +++ b/models/Readme.md @@ -1,3 +1,7 @@ # Infinity Engine File Formats Models Please read [Infinity Engine File Formats](https://gibberlings3.github.io/iesdp/file_formats/index.htm) + +## Binrw + +This crate heavily relies on [binrw](https://github.com/jam1garner/binrw), see [here](https://docs.rs/binrw/0.14.0/binrw/docs/attribute/index.html) for the macro magic. diff --git a/models/fixtures/#trollis.eff b/models/fixtures/#trollis.eff old mode 100755 new mode 100644 diff --git a/models/fixtures/AR0002.ARE b/models/fixtures/AR0002.ARE new file mode 100644 index 0000000..31139f4 Binary files /dev/null and b/models/fixtures/AR0002.ARE differ diff --git a/models/fixtures/AR0011.ARE b/models/fixtures/AR0011.ARE new file mode 100644 index 0000000..c1b781f Binary files /dev/null and b/models/fixtures/AR0011.ARE differ diff --git a/models/fixtures/AR0226.ARE b/models/fixtures/AR0226.ARE new file mode 100644 index 0000000..ae51b35 Binary files /dev/null and b/models/fixtures/AR0226.ARE differ diff --git a/models/fixtures/bdparty.BAF b/models/fixtures/bdparty.BAF new file mode 100644 index 0000000..c08724c --- /dev/null +++ b/models/fixtures/bdparty.BAF @@ -0,0 +1,659 @@ +IF + !InParty(Myself) + Global("bd_joined","locals",1) + AreaCheck("bd4700") // Avernus Roof + GlobalLT("bd_plot","global",570) +THEN + RESPONSE #100 + StartDialogueNoSet(Player1) +END + +IF + !InParty(Myself) + Global("bd_no_boot_dialog","myarea",0) + Global("bd_joined","locals",1) + OR(12) + Name("neera",Myself) // Neera + Name("dorn",Myself) // Dorn + Name("rasaad",Myself) // Rasaad + Name("baeloth",Myself) // Baeloth + Name("edwin",Myself) // Edwin + Name("viconia",Myself) // Viconia + Name("safana",Myself) // Safana + Name("jaheira",Myself) // Jaheira + Name("khalid",Myself) // Khalid + Name("dynaheir",Myself) // Dynaheir + Name("minsc",Myself) // Minsc + HappinessGT(Myself,-299) +THEN + RESPONSE #100 + SetGlobal("bd_joined","locals",2) + Dialogue(Player1) +END + +IF + !InParty(Myself) + Global("bd_no_boot_dialog","myarea",0) + Global("bd_joined","locals",1) + OR(4) + Name("corwin",Myself) // Corwin + Name("glint",Myself) // Glint + Name("voghiln",Myself) // Voghiln + Name("mkhiin",Myself) // M'Khiin + HappinessLT(Myself,-290) +THEN + RESPONSE #100 + VerbalConstant(Myself,UNHAPPY_BREAKING_POINT) + SetGlobal("bd_joined","locals",0) + SetGlobal("bd_npc_camp","locals",1) +END + +IF + !InParty(Myself) + Global("bd_no_boot_dialog","myarea",0) + Global("bd_joined","locals",1) +THEN + RESPONSE #100 + VerbalConstant(Myself,UNHAPPY_BREAKING_POINT) + Wait(5) + EscapeArea() +END + +IF + Global("bd_npc_camp","locals",1) + Name("baeloth",Myself) // Baeloth + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("baeloth")) // Baeloth +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",135,3575,NE) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",775,3530,NW) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1110,1795,S) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("baeloth",Myself) // Baeloth + TriggerOverride("ff_camp",IsOverMe("baeloth")) // Baeloth + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[135.3575]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[775.3530]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1110.1795]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("corwin",Myself) // Corwin + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("corwin")) // Corwin +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",560,3515,SW) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",475,3270,SE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1355,215,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("corwin",Myself) // Corwin + TriggerOverride("ff_camp",IsOverMe("corwin")) // Corwin + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[560.3515]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[475.3270]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1355.215]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("dorn",Myself) // Dorn + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("dorn")) // Dorn +THEN + RESPONSE #3 + EscapeAreaMove("bd7100",600,3615,N) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1130,1635,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("dorn",Myself) // Dorn + TriggerOverride("ff_camp",IsOverMe("dorn")) // Dorn + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[600.3615]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1130.1635]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("dynaheir",Myself) // Dynaheir + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("dynaheir")) // Dynaheir +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",735,3755,NW) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",215,3820,NE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1385,1685,W) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("dynaheir",Myself) // Dynaheir + TriggerOverride("ff_camp",IsOverMe("dynaheir")) // Dynaheir + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[735.3755]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[215.3820]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1385.1685]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("edwin",Myself) // Edwin + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("edwin")) // Edwin +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",230,3460,S) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",375,3535,E) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1812,1711,S) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("edwin",Myself) // Edwin + TriggerOverride("ff_camp",IsOverMe("edwin")) // Edwin + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[230.3460]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[375.3535]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1812.1711]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("glint",Myself) // Glint + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("glint")) // Glint +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",410,3530,NE) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",435,3370,NE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1055,1511,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("glint",Myself) // Glint + TriggerOverride("ff_camp",IsOverMe("glint")) // Glint + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[410.3530]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[435.3370]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1055.1511]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("jaheira",Myself) // Jaheira + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("jaheira")) // Jaheira +THEN + RESPONSE #3 + EscapeAreaMove("bd7100",185,3645,S) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1445,1805,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("jaheira",Myself) // Jaheira + TriggerOverride("ff_camp",IsOverMe("jaheira")) // Jaheira + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[185.3645]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1445.1805]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("khalid",Myself) // Khalid + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("khalid")) // Khalid +THEN + RESPONSE #3 + EscapeAreaMove("bd7100",225,3670,SW) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1495,1780,S) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("khalid",Myself) // Khalid + TriggerOverride("ff_camp",IsOverMe("khalid")) // Khalid + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[225.3670]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1495.1780]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("minsc",Myself) // Minsc + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("minsc")) // Minsc +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",700,3785,N) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",200,3770,E) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1365,1735,NW) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("minsc",Myself) // Minsc + TriggerOverride("ff_camp",IsOverMe("minsc")) // Minsc + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[700.3785]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[200.3770]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1365.1735]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("mkhiin",Myself) // M'Khiin + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("mkhiin")) // M'Khiin +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",495,3205,SE) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",85,3590,SE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",2145,1345,SW) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("mkhiin",Myself) // M'Khiin + TriggerOverride("ff_camp",IsOverMe("mkhiin")) // M'Khiin + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[495.3205]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[85.3590]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[2145.1345]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("neera",Myself) // Neera + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("neera")) // Neera +THEN + RESPONSE #3 + EscapeAreaMove("bd7100",350,3735,SW) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1605,1720,NE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("neera",Myself) // Neera + TriggerOverride("ff_camp",IsOverMe("neera")) // Neera + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[350.3735]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1605.1720]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("rasaad",Myself) // Rasaad + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("rasaad")) // Rasaad +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",595,3780,NE) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",295,3725,S) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1545,1805,SW) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("rasaad",Myself) // Rasaad + TriggerOverride("ff_camp",IsOverMe("rasaad")) // Rasaad + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[595.3780]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[295.3725]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1545.1805]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("safana",Myself) // Safana + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("safana")) // Safana +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",280,3480,SW) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",405,3495,SE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1509,1490,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("safana",Myself) // Safana + TriggerOverride("ff_camp",IsOverMe("safana")) // Safana + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[280.3480]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[405.3495]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1509.1490]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("viconia",Myself) // Viconia + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("viconia")) // Viconia +THEN + RESPONSE #2 + EscapeAreaMove("bd1000",165,3470,SE) // Coast Way Crossing + RESPONSE #3 + EscapeAreaMove("bd7100",310,3365,SE) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1799,1804,SE) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("viconia",Myself) // Viconia + TriggerOverride("ff_camp",IsOverMe("viconia")) // Viconia + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #2 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[165.3470]) + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[310.3365]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1799.1804]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + Global("bd_npc_camp","locals",1) + Name("voghiln",Myself) // Voghiln + Switch("bd_npc_camp_chapter","global") + OR(2) + !Range("ff_camp",999) + !TriggerOverride("ff_camp",IsOverMe("voghiln")) // Voghiln +THEN + RESPONSE #3 + EscapeAreaMove("bd7100",455,3490,S) // Troll Forest + RESPONSE #4 + EscapeAreaMove("bd3000",1573,1478,SW) // Allied Siege Camp +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) + Name("voghiln",Myself) // Voghiln + TriggerOverride("ff_camp",IsOverMe("voghiln")) // Voghiln + Switch("bd_npc_camp_chapter","global") +THEN + RESPONSE #3 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[455.3490]) + RESPONSE #4 + SetGlobal("bd_npc_camp","locals",2) + SaveLocation("LOCALS","bd_default_loc",[1573.1478]) + ChangeAIScript("bdasc3",CLASS) +END + +IF + GlobalLT("bd_npc_camp","locals",2) + Global("bd_joined","locals",0) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",2) + SaveObjectLocation("LOCALS","bd_default_loc",Myself) +END + +IF + Global("bd_npc_camp","locals",2) + OR(2) + Class(Myself,CLERIC_MAGE) + Class(Myself,FIGHTER_MAGE_CLERIC) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdclma01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) + OR(2) + Class(Myself,MAGE_ALL) + Class(Myself,BARD_ALL) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdmage01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) + Class(Myself,CLERIC_ALL) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdcler01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) + Class(Myself,DRUID_ALL) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bddrui01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) + Class(Myself,THIEF_ALL) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdthie01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) + Class(Myself,MONK) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdmonk01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",2) +THEN + RESPONSE #100 + SetGlobal("bd_npc_camp","locals",3) + SetGlobal("bd_no_combat","locals",0) + ChangeAIScript("bdfigh01",GENERAL) +END + +IF + Global("bd_npc_camp","locals",3) +THEN + RESPONSE #100 + SetGlobal("bd_joined","locals",0) + SetGlobal("bd_retreat","locals",1) + SetGlobal("bd_no_aggro","locals",1) + SetGlobal("bd_no_assist","locals",1) + SetGlobal("bd_no_search","locals",1) + SetGlobal("bd_no_combat","locals",1) + ChangeEnemyAlly(Myself,NEUTRAL) + ChangeSpecifics(Myself,ALLIES) + ChangeAIScript("bdshout",RACE) + ChangeAIScript("",DEFAULT) +END + diff --git a/models/fixtures/chitin.key b/models/fixtures/chitin.key old mode 100755 new mode 100644 diff --git a/models/fixtures/cutmelis.cre b/models/fixtures/cutmelis.cre old mode 100755 new mode 100644 diff --git a/models/fixtures/dbeggar.cre b/models/fixtures/dbeggar.cre old mode 100755 new mode 100644 diff --git a/models/fixtures/dialog.tlk b/models/fixtures/dialog.tlk old mode 100755 new mode 100644 diff --git a/models/fixtures/gate1.spl b/models/fixtures/gate1.spl old mode 100755 new mode 100644 diff --git a/models/fixtures/mazzy.dlg b/models/fixtures/mazzy.dlg old mode 100755 new mode 100644 diff --git a/models/fixtures/soundoff.ids b/models/fixtures/soundoff.ids deleted file mode 100755 index 6b24fbc..0000000 --- a/models/fixtures/soundoff.ids +++ /dev/null @@ -1,38 +0,0 @@ -IDS V1.0 -0 INITIAL_MEETING -1 MORALE -2 HAPPY -3 UNHAPPY_ANNOYED -4 UNHAPPY_SERIOUS -5 UNHAPPY_BREAKING_POINT -6 LEADER -7 TIRED -8 BORED -9 BATTLE_CRY -14 ATTACK -18 DAMAGE -19 DYING -20 HURT -21 AREA_FOREST -22 AREA_CITY -23 AREA_DUNGEON -24 AREA_DAY -25 AREA_NIGHT -26 SELECT_COMMON -32 SELECT_ACTION -35 SELECT_RARE -39 INTERACTION -44 INSULT -47 COMPLIMENT -50 SPECIAL -53 REACT_TO_DIE_GENERAL -54 REACT_TO_DIE_SPECIFIC -55 MISCELLANEOUS -55 RESPONSE_TO_COMPLIMENT -58 RESPONSE_TO_INSULT -59 DIALOG_HOSTILE -60 DIALOG_DEFAULT -10 BATTLE_CRY2 -11 BATTLE_CRY3 -12 BATTLE_CRY4 -13 BATTLE_CRY5 diff --git a/models/fixtures/sw1h01.itm b/models/fixtures/sw1h01.itm old mode 100755 new mode 100644 diff --git a/models/fixtures/test.bio b/models/fixtures/test.bio new file mode 100644 index 0000000..1324402 --- /dev/null +++ b/models/fixtures/test.bio @@ -0,0 +1,11 @@ +Your history is nearly as unknown as your future, and the things that are certain seem more fancy than fact. As unlikely as it may seem, you have the blood of a deity coursing through your veins. + +You are a product of the Time of Troubles, a cataclysmic period when the gods were made flesh and forced to walk the earth among their followers. One such deity foresaw both the event and his inevitable death because of it, and so took steps to effect his resurrection. This god strode the land before he was made to, and in his wake left a score of mortal progeny driven to conquer and rule. They were not intended to be his successors, but rather the fuel for his rebirth. Heroes would rise to counter these tyrants, and when their evil fell, it would fall to the father. + +The god was Bhaal, Lord of Murder, and you are one of his children. + +You were cared for as a child by Gorion, a powerful wizard that may have had even more powerful friends. It was his influence that allowed you to spend your youth in the library fortress of Candlekeep, where the resident monks schooled you in your skills. Children were an oddity at the keep, though you did have a friend in Imoen. She seemed a kindred spirit, though you knew no more of her background than your own. Gorion never explained how you or she came to be there, or why you needed such a secluded home. It was only after his death that you learned the truth about your bloodline, when you were forced into conflict with his killer, another child of Bhaal. + +Sarevok was this sibling's name, and he had embraced his foul origins, determined to exploit them and become the next Lord of Murder. He sought to create death on a massive scale, a war of sacrifice that would prove his claim to his father's throne. You unraveled his carefully wrought plans, and ultimately it was you that took his life, sending his taint back to Bhaal. A victory of sorts... + +Now you face an uncertain future. A child of murder, you have a lineage that will tempt the ignorant to fear you and the unscrupulous to use you. And always the essence of Bhaal is within, exerting its dark pull when you are weakest. Which is the greater fear: Losing your life to fuel the fire, or losing your will and becoming it? \ No newline at end of file diff --git a/models/fixtures/xpcap.2da b/models/fixtures/xpcap.2da deleted file mode 100755 index 5eee9b1..0000000 --- a/models/fixtures/xpcap.2da +++ /dev/null @@ -1,24 +0,0 @@ -2DA V1.0 -2950000 - VALUE -MAGE -1 -FIGHTER -1 -CLERIC -1 -THIEF -1 -BARD -1 -PALADIN -1 -DRUID -1 -RANGER -1 -FIGHTER_MAGE -1 -FIGHTER_CLERIC -1 -FIGHTER_THIEF -1 -FIGHTER_MAGE_THIEF -1 -MAGE_THIEF -1 -CLERIC_MAGE -1 -CLERIC_THIEF -1 -FIGHTER_DRUID -1 -FIGHTER_MAGE_CLERIC -1 -CLERIC_RANGER -1 -MONK -1 -SORCERER -1 -SHAMAN -1 diff --git a/models/src/area.rs b/models/src/area.rs index b32d2b0..eee56a0 100644 --- a/models/src/area.rs +++ b/models/src/area.rs @@ -1,138 +1,79 @@ use std::rc::Rc; +use binrw::{io::Cursor, io::SeekFrom, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::header::Header; -use crate::item_table::ItemReferenceTable; +use crate::common::resref::Resref; +use crate::common::strref::Strref; use crate::model::Model; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; use crate::tlk::Lookup; -use crate::{common::fixed_char_array::FixedCharSlice, game::GlobalVariables}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Area { + #[serde(flatten)] pub header: FileHeader, + #[serde(flatten)] + #[br(count=header.count_of_actors, seek_before=SeekFrom::Start(header.offset_to_actors as u64))] pub actors: Vec, + #[serde(flatten)] + #[br(count=header.count_of_regions, seek_before=SeekFrom::Start(header.offset_to_regions as u64))] pub regions: Vec, + #[serde(flatten)] + #[br(count=header.count_of_spawn_points, seek_before=SeekFrom::Start(header.offset_to_spawn_points as u64))] pub spawn_points: Vec, + #[serde(flatten)] + #[br(count=header.count_of_entrances, seek_before=SeekFrom::Start(header.offset_to_entrances as u64))] pub entrances: Vec, + #[serde(flatten)] + #[br(count=header.count_of_containers, seek_before=SeekFrom::Start(header.offset_to_containers as u64))] pub containers: Vec, - pub items: Vec, + #[serde(flatten)] + #[br(count=header.count_of_items, seek_before=SeekFrom::Start(header.offset_to_items as u64))] + pub items: Vec, + #[serde(flatten)] + #[br(count=header.count_of_vertices, seek_before=SeekFrom::Start(header.offset_to_vertices as u64))] pub vertices: Vec, + #[serde(flatten)] + #[br(count=header.count_of_ambients, seek_before=SeekFrom::Start(header.offset_to_ambients as u64))] pub ambients: Vec, - pub variables: Vec, + #[serde(flatten)] + #[br(count=header.count_of_variables, seek_before=SeekFrom::Start(header.offset_to_variables as u64))] + pub variables: Vec, + #[serde(flatten)] + #[br(count=header.size_of_explored_bitmask, seek_before=SeekFrom::Start(header.offset_to_explored_bitmask as u64))] pub explored_bitmasks: Vec, + #[serde(flatten)] + #[br(count=header.count_of_doors, seek_before=SeekFrom::Start(header.offset_to_doors as u64))] pub doors: Vec, + #[serde(flatten)] + #[br(count=header.count_of_animations, seek_before=SeekFrom::Start(header.offset_to_animations as u64))] pub animations: Vec, + #[serde(flatten)] + #[br(count=header.count_of_automap_notes, seek_before=SeekFrom::Start(header.offset_to_automap_notes as u64))] pub automap_notes: Vec, + #[serde(flatten)] + #[br(count=header.count_of_tiled_objects,seek_before=SeekFrom::Start(header.offset_to_tiled_objects as u64))] pub tiled_objects: Vec, - pub tiled_object_flags: Vec, + #[serde(flatten)] + #[br(count=header.number_of_entries_in_the_projectile_traps, seek_before=SeekFrom::Start(header.offset_to_projectile_traps as u64))] pub projectile_traps: Vec, - pub songs: Vec, - pub rest_interruptions: Vec, + #[serde(flatten)] + #[br(seek_before=SeekFrom::Start(header.offset_to_song_entries as u64))] + pub songs: SongEntry, + #[serde(flatten)] + #[br(seek_before=SeekFrom::Start(header.offset_to_rest_interruptions as u64))] + pub rest_interruptions: RestInterruption, } impl Model for Area { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_actors).unwrap_or(0); - let count = usize::try_from(header.count_of_actors).unwrap_or(0); - let actors = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_regions).unwrap_or(0); - let count = usize::try_from(header.count_of_regions).unwrap_or(0); - let regions = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_spawn_points).unwrap_or(0); - let count = usize::try_from(header.count_of_spawn_points).unwrap_or(0); - let spawn_points = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_entrances).unwrap_or(0); - let count = usize::try_from(header.count_of_entrances).unwrap_or(0); - let entrances = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_containers).unwrap_or(0); - let count = usize::try_from(header.count_of_containers).unwrap_or(0); - let containers = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_items).unwrap_or(0); - let count = usize::try_from(header.count_of_items).unwrap_or(0); - let items = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_vertices).unwrap_or(0); - let count = usize::try_from(header.count_of_vertices).unwrap_or(0); - let vertices = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_ambients).unwrap_or(0); - let count = usize::try_from(header.count_of_ambients).unwrap_or(0); - let ambients = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_variables).unwrap_or(0); - let count = usize::try_from(header.count_of_variables).unwrap_or(0); - let variables = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_tiled_object_flags).unwrap_or(0); - let count = usize::try_from(header.count_of_tiled_object_flags).unwrap_or(0); - let tiled_object_flags = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_doors).unwrap_or(0); - let count = usize::try_from(header.count_of_doors).unwrap_or(0); - let doors = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_animations).unwrap_or(0); - let count = usize::try_from(header.count_of_animations).unwrap_or(0); - let animations = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_tiled_objects).unwrap_or(0); - let count = usize::try_from(header.count_of_tiled_objects).unwrap_or(0); - let tiled_objects = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_explored_bitmask).unwrap_or(0); - let count = usize::try_from(header.size_of_explored_bitmask).unwrap_or(1) - / std::mem::size_of::(); - let explored_bitmasks = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_automap_notes).unwrap_or(0); - let count = usize::try_from(header.number_of_entries_in_the_automap_notes).unwrap_or(0); - let automap_notes = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_projectile_traps).unwrap_or(0); - let count = usize::try_from(header.number_of_entries_in_the_projectile_traps).unwrap_or(0); - let projectile_traps = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_song_entries).unwrap_or(0); - let count = if start > 0 { 0 } else { start + 144 }; - let songs = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_rest_interruptions).unwrap_or(0); - let count = if start > 0 { 0 } else { start + 228 }; - let rest_interruptions = copy_transmute_buff::(buffer, start, count); - - Self { - header, - actors, - regions, - spawn_points, - entrances, - containers, - items, - vertices, - ambients, - variables, - tiled_object_flags, - doors, - animations, - tiled_objects, - explored_bitmasks, - automap_notes, - projectile_traps, - songs, - rest_interruptions, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } @@ -141,102 +82,35 @@ impl Model for Area { } fn name(&self, _lookup: &Lookup) -> String { - self.header.area_wed.to_string().replace(".WED", ".ARE") + todo!() } fn to_bytes(&self) -> Vec { - let mut out = vec![ - (1, to_u8_slice(&self.header).to_vec()), - ( - self.header.offset_to_actors as i32, - vec_to_u8_slice(&self.actors), - ), - ( - self.header.offset_to_regions, - vec_to_u8_slice(&self.regions), - ), - ( - self.header.offset_to_spawn_points, - vec_to_u8_slice(&self.spawn_points), - ), - ( - self.header.offset_to_entrances, - vec_to_u8_slice(&self.entrances), - ), - ( - self.header.offset_to_containers, - vec_to_u8_slice(&self.containers), - ), - (self.header.offset_to_items, vec_to_u8_slice(&self.items)), - ( - self.header.offset_to_vertices, - vec_to_u8_slice(&self.vertices), - ), - ( - self.header.offset_to_ambients, - vec_to_u8_slice(&self.ambients), - ), - ( - self.header.offset_to_variables, - vec_to_u8_slice(&self.variables), - ), - ( - self.header.offset_to_tiled_object_flags as i32, - vec_to_u8_slice(&self.tiled_object_flags), - ), - ( - self.header.offset_to_explored_bitmask as i32, - vec_to_u8_slice(&self.explored_bitmasks), - ), - (self.header.offset_to_doors, vec_to_u8_slice(&self.doors)), - ( - self.header.offset_to_animations, - vec_to_u8_slice(&self.animations), - ), - ( - self.header.offset_to_tiled_objects, - vec_to_u8_slice(&self.tiled_objects), - ), - ( - self.header.offset_to_song_entries, - vec_to_u8_slice(&self.songs), - ), - ( - self.header.offset_to_rest_interruptions, - vec_to_u8_slice(&self.rest_interruptions), - ), - ( - self.header.number_of_entries_in_the_automap_notes, - vec_to_u8_slice(&self.automap_notes), - ), - ( - self.header.number_of_entries_in_the_automap_notes, - vec_to_u8_slice(&self.projectile_traps), - ), - ]; - out.sort_by(|a, b| a.0.cmp(&b.0)); - out.into_iter() - .filter(|data| data.0 < 1 && !data.1.is_empty()) - .flat_map(|(_order, data)| data) - .collect() + vec![] } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct FileHeader { - pub header: Header<4, 4>, - pub area_wed: FixedCharSlice<8>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, + pub area_wed: Resref, pub last_saved: u32, pub area_flags: u32, - pub resref_of_the_area_to_the_north_of_this_area: FixedCharSlice<8>, + pub resref_of_the_area_to_the_north_of_this_area: Resref, pub north_area_flags: u32, - pub resref_of_the_area_to_the_east_of_this_area: FixedCharSlice<8>, + pub resref_of_the_area_to_the_east_of_this_area: Resref, pub east_area_flags: u32, - pub resref_of_the_area_to_the_south_of_this_area: FixedCharSlice<8>, + pub resref_of_the_area_to_the_south_of_this_area: Resref, pub south_area_flags: u32, - pub resref_of_the_area_to_the_west_of_this_area: FixedCharSlice<8>, + pub resref_of_the_area_to_the_west_of_this_area: Resref, pub west_area_flags: u32, pub area_type_flags: u16, pub rain_probability: u16, @@ -246,60 +120,63 @@ pub struct FileHeader { pub lightning_probability: u16, pub wind_speed: u16, pub offset_to_actors: u32, - pub count_of_actors: i16, - pub count_of_regions: i16, - pub offset_to_regions: i32, - pub offset_to_spawn_points: i32, - pub count_of_spawn_points: i32, - pub offset_to_entrances: i32, - pub count_of_entrances: i32, - pub offset_to_containers: i32, - pub count_of_containers: i16, - pub count_of_items: i16, - pub offset_to_items: i32, - pub offset_to_vertices: i32, - pub count_of_vertices: i16, - pub count_of_ambients: i16, - pub offset_to_ambients: i32, - pub offset_to_variables: i32, - pub count_of_variables: i32, - pub offset_to_tiled_object_flags: i16, - pub count_of_tiled_object_flags: i16, - pub area_script: FixedCharSlice<8>, + pub count_of_actors: u16, + pub count_of_regions: u16, + pub offset_to_regions: u32, + pub offset_to_spawn_points: u32, + pub count_of_spawn_points: u32, + pub offset_to_entrances: u32, + pub count_of_entrances: u32, + pub offset_to_containers: u32, + pub count_of_containers: u16, + pub count_of_items: u16, + pub offset_to_items: u32, + pub offset_to_vertices: u32, + pub count_of_vertices: u16, + pub count_of_ambients: u16, + pub offset_to_ambients: u32, + pub offset_to_variables: u32, + pub count_of_variables: u32, + pub offset_to_tiled_object_flags: u16, + pub count_of_tiled_object_flags: u16, + pub area_script: Resref, pub size_of_explored_bitmask: u32, pub offset_to_explored_bitmask: u32, - pub count_of_doors: i32, - pub offset_to_doors: i32, - pub count_of_animations: i32, - pub offset_to_animations: i32, - pub count_of_tiled_objects: i32, - pub offset_to_tiled_objects: i32, - pub offset_to_song_entries: i32, - pub offset_to_rest_interruptions: i32, - pub offset_to_automap_notes: i32, - pub number_of_entries_in_the_automap_notes: i32, - pub offset_to_projectile_traps: i32, - pub number_of_entries_in_the_projectile_traps: i32, + pub count_of_doors: u32, + pub offset_to_doors: u32, + pub count_of_animations: u32, + pub offset_to_animations: u32, + pub count_of_tiled_objects: u32, + pub offset_to_tiled_objects: u32, + pub offset_to_song_entries: u32, + pub offset_to_rest_interruptions: u32, + pub offset_to_automap_notes: u32, + pub count_of_automap_notes: u32, + pub offset_to_projectile_traps: u32, + pub number_of_entries_in_the_projectile_traps: u32, // bgee and bg2:tob - pub rest_movie_day: FixedCharSlice<8>, + pub rest_movie_day: Resref, // bgee and bg2:tob - pub rest_movie_night: FixedCharSlice<8>, + pub rest_movie_night: Resref, #[serde(skip)] - _unused: FixedCharSlice<56>, + #[br(count = 56)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Actor -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct Actor { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub current_x_coordinate: u16, pub current_y_coordinate: u16, pub destination_x_coordinate: u16, pub destination_y_coordinate: u16, pub flags: u32, pub has_been_spawned: u16, - pub first_letter_of_cre_resref: FixedCharSlice<1>, + pub first_letter_of_cre_resref: u8, #[serde(skip)] _unused_1: u8, pub actor_animation: u32, @@ -310,26 +187,29 @@ pub struct Actor { pub movement_restriction_distance_move_to_object: u16, pub actor_appearence_schedule: u32, pub num_times_talked_to: u32, - pub dialog: FixedCharSlice<8>, - pub script_override: FixedCharSlice<8>, - pub script_general: FixedCharSlice<8>, - pub script_class: FixedCharSlice<8>, - pub script_race: FixedCharSlice<8>, - pub script_default: FixedCharSlice<8>, - pub script_specific: FixedCharSlice<8>, - pub cre_file: FixedCharSlice<8>, + pub dialog: Resref, + pub script_override: Resref, + pub script_general: Resref, + pub script_class: Resref, + pub script_race: Resref, + pub script_default: Resref, + pub script_specific: Resref, + pub cre_file: Resref, // for embedded cre files pub offset_to_cre_structure: u32, pub size_of_stored_cre_structure: u32, #[serde(skip)] - _unused_2: FixedCharSlice<128>, + #[br(count = 128)] + _unused_2: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Info -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Region { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub region_type: u16, pub minimum_bounding_box_of_this_point: [u16; 4], pub count_of_vertices_composing_the_perimeter: u16, @@ -337,12 +217,15 @@ pub struct Region { pub trigger_value: u32, pub cursor_index: u32, // for travel regions - pub destination_area: FixedCharSlice<8>, + pub destination_area: Resref, // for travel regions - pub entrance_name_in_destination_area: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub entrance_name_in_destination_area: String, pub flags: u32, // for info points - pub information_text: FixedCharSlice<4>, + pub information_text: Strref, pub trap_detection_difficulty_percent: u16, pub trap_removal_difficulty_percent: u16, // 0=no, 1=yes @@ -350,33 +233,36 @@ pub struct Region { // 0=no, 1=yes pub trap_detected: u16, pub trap_launch_location: [u16; 2], - pub key_item: FixedCharSlice<8>, - pub region_script: FixedCharSlice<8>, + pub key_item: Resref, + pub region_script: Resref, pub alternative_use_point_x_coordinate: u16, pub alternative_use_point_y_coordinate: u16, #[serde(skip)] _unknown_1: u32, #[serde(skip)] - _unknown_2: [u8; 32], + #[br(count = 32)] + _unknown_2: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Spawn -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct SpawnPoint { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub x_coordinate: u16, pub y_coordinate: u16, - pub resref_of_creature_to_spawn_1st: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_2nd: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_3rd: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_4th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_5th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_6th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_7th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_8th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_9th: FixedCharSlice<8>, - pub resref_of_creature_to_spawn_10th: FixedCharSlice<8>, + pub resref_of_creature_to_spawn_1st: Resref, + pub resref_of_creature_to_spawn_2nd: Resref, + pub resref_of_creature_to_spawn_3rd: Resref, + pub resref_of_creature_to_spawn_4th: Resref, + pub resref_of_creature_to_spawn_5th: Resref, + pub resref_of_creature_to_spawn_6th: Resref, + pub resref_of_creature_to_spawn_7th: Resref, + pub resref_of_creature_to_spawn_8th: Resref, + pub resref_of_creature_to_spawn_9th: Resref, + pub resref_of_creature_to_spawn_10th: Resref, pub count_of_spawn_creatures: u16, pub base_creature_number_to_spawn: u16, pub frequency: u16, @@ -414,26 +300,32 @@ pub struct SpawnPoint { // Offset 0x006c pub spawn_weight_of_10th_creature_slot: u8, #[serde(skip)] - _unused: FixedCharSlice<38>, + #[br(count = 38)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Entrance -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Entrance { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub x_coordinate: u16, pub y_coordinate: u16, pub orientation: u16, #[serde(skip)] - _unused: FixedCharSlice<66>, + #[br(count = 66)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Container -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Container { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub x_coordinate: u16, pub y_coordinate: u16, pub container_type: u16, @@ -453,33 +345,44 @@ pub struct Container { pub bottom_bounding_box_of_container_polygon: u16, pub index_to_first_item_in_this_container: u32, pub count_of_items_in_this_container: u32, - pub trap_script: FixedCharSlice<8>, + pub trap_script: Resref, pub index_to_first_vertex_of_the_outline: u32, pub count_of_vertices_making_up_the_outline: u16, pub trigger_range: u16, - pub owner_script_name: FixedCharSlice<32>, - pub key_item: FixedCharSlice<8>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub owner_script_name: String, + pub key_item: Resref, pub break_difficulty: u32, - pub lockpick_string: FixedCharSlice<4>, + pub lockpick_string: Strref, #[serde(skip)] - _unused: FixedCharSlice<56>, + #[br(count = 56)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Item -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub struct AreaItem(pub ItemReferenceTable); +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] +pub struct Item { + pub item_resref: Resref, + pub item_expiration_time: u16, + pub quantity_1: u16, + pub quantity_2: u16, + pub quantity_3: u16, + pub flags: u32, +} // An array of points used to create the outlines of regions and containers. Elements are 16-bit words stored x0, y0, x1, y1 etc. -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Vertice(pub u16); // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Ambient -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Ambient { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub x_coordinate: u16, pub y_coordinate: u16, pub radius: u16, @@ -487,16 +390,16 @@ pub struct Ambient { pub pitch_variance: u32, pub volume_variance: u16, pub volume_percentage: u16, - pub resref_of_sound_1: FixedCharSlice<8>, - pub resref_of_sound_2: FixedCharSlice<8>, - pub resref_of_sound_3: FixedCharSlice<8>, - pub resref_of_sound_4: FixedCharSlice<8>, - pub resref_of_sound_5: FixedCharSlice<8>, - pub resref_of_sound_6: FixedCharSlice<8>, - pub resref_of_sound_7: FixedCharSlice<8>, - pub resref_of_sound_8: FixedCharSlice<8>, - pub resref_of_sound_9: FixedCharSlice<8>, - pub resref_of_sound_10: FixedCharSlice<8>, + pub resref_of_sound_1: Resref, + pub resref_of_sound_2: Resref, + pub resref_of_sound_3: Resref, + pub resref_of_sound_4: Resref, + pub resref_of_sound_5: Resref, + pub resref_of_sound_6: Resref, + pub resref_of_sound_7: Resref, + pub resref_of_sound_8: Resref, + pub resref_of_sound_9: Resref, + pub resref_of_sound_10: Resref, pub count_of_sounds: u16, #[serde(skip)] _unused_1: u16, @@ -505,26 +408,52 @@ pub struct Ambient { pub ambient_appearence_schedule: u32, pub flags: u32, #[serde(skip)] - _unused_2: FixedCharSlice<64>, + #[br(count = 64)] + _unused_2: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Variable -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub struct AreaVariable(pub GlobalVariables); +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] +pub struct Variable { + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, + /* + bit 0: int + bit 1: float + bit 2: script name + bit 3: resref + bit 4: strref + bit 5: dword + */ + pub variable_type: u16, + pub resource_value: u16, + pub dword_value: u32, + pub int_value: u32, + pub double_value: i64, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub script_name_value: String, +} // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Explored -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ExploredBitmask(pub u8); // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Door -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Door { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, // Link with WED - pub door_id: FixedCharSlice<8>, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub door_id: String, pub flags: u32, pub index_of_first_vertex_of_the_door_outline_when_open: u32, pub count_of_vertices_of_the_door_outline_when_open: u16, @@ -538,8 +467,8 @@ pub struct Door { pub index_of_first_vertex_in_the_impeded_cell_block_when_closed: u32, pub hit_points: u16, pub armor_class: u16, - pub door_open_sound: FixedCharSlice<8>, - pub door_close_sound: FixedCharSlice<8>, + pub door_open_sound: Resref, + pub door_close_sound: Resref, pub cursor_index: u32, pub trap_detection_difficulty: u16, pub trap_removal_difficulty: u16, @@ -549,30 +478,36 @@ pub struct Door { pub trap_detected: u16, pub trap_launch_target_x_coordinate: u16, pub trap_launch_target_y_coordinate: u16, - pub key_item: FixedCharSlice<8>, - pub door_script: FixedCharSlice<8>, + pub key_item: Resref, + pub door_script: Resref, // Secret doors pub detection_difficulty: u32, pub lock_difficulty: u32, pub two_points: [u16; 4], - pub lockpick_string: FixedCharSlice<4>, - pub travel_trigger_name: FixedCharSlice<24>, - pub dialog_speaker_name: FixedCharSlice<4>, - pub dialog_resref: FixedCharSlice<8>, + pub lockpick_string: Strref, + #[br(count = 24)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub travel_trigger_name: String, + pub dialog_speaker_name: Strref, + pub dialog_resref: Resref, #[serde(skip)] - _unknown: FixedCharSlice<8>, + #[br(count = 8)] + _unknown: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Anim -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Animation { - pub name: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, pub x_coordinate: u16, pub y_coordinate: u16, pub animation_appearence_schedule: u32, // bgee: bam/wbm/pvrz, others: bam - pub animation_resref: FixedCharSlice<8>, + pub animation_resref: Resref, pub bam_sequence_number: u16, pub bam_frame_number: u16, pub flags: u32, @@ -583,7 +518,7 @@ pub struct Animation { // 0 defaults to 100 pub chance_of_looping: u8, pub skip_cycles: u8, - pub palette: FixedCharSlice<8>, + pub palette: Resref, // note: only required for wbm and pvrz resources (see flags bit 13/15) pub animation_width: u16, // only required for wbm and pvrz resources (see flags bit 13/15) @@ -591,41 +526,43 @@ pub struct Animation { } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Automap -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct AutomapNotesBGEE { pub x_coordinate: u16, pub y_coordinate: u16, - pub note_text: FixedCharSlice<4>, + pub note_text: Strref, // 0=external (toh/tot) or 1=internal (tlk) pub strref_location: u16, // bg2 pub colour: u16, pub note_count: u32, #[serde(skip)] - _unused: FixedCharSlice<36>, + #[br(count = 36)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_TiledObj -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct TiledObject { - pub name: FixedCharSlice<32>, - pub tile_id: FixedCharSlice<8>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, + pub tile_id: Resref, pub flags: u32, pub offset_to_open_search_squares: u32, pub count_of_open_search_squares: u16, pub count_of_closed_search_squares: u16, pub offset_to_closed_search_squares: u32, #[serde(skip)] - _unused: FixedCharSlice<48>, + #[br(count = 48)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_ProjTraps -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct ProjectileTrap { - pub projectile_resref: FixedCharSlice<8>, + pub projectile_resref: Resref, pub effect_block_offset: u32, pub effect_block_size: u16, pub missile_ids_reference: u16, @@ -635,12 +572,11 @@ pub struct ProjectileTrap { pub y_coordinate: u16, pub z_coordinate: u16, pub enemy_ally_targetting: u8, - pub creatorparty_member_index: u8, + pub party_member_index: u8, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Song_entries -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct SongEntry { pub day_song_reference_number: u32, pub night_song_reference_number: u32, @@ -652,24 +588,31 @@ pub struct SongEntry { pub alt_music_3: u32, pub alt_music_4: u32, pub alt_music_5: u32, - pub main_day_ambient_1: FixedCharSlice<8>, - pub main_day_ambient_2: FixedCharSlice<8>, + pub main_day_ambient_1: Resref, + pub main_day_ambient_2: Resref, pub main_day_ambient_volume_percent: u32, - pub main_night_ambient_1: FixedCharSlice<8>, - pub main_night_ambient_2: FixedCharSlice<8>, + pub main_night_ambient_1: Resref, + pub main_night_ambient_2: Resref, pub main_night_ambient_volume_percent: u32, pub reverb_or_unused: u32, #[serde(skip)] - _unused: FixedCharSlice<60>, + #[br(count = 60)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/are_v1.htm#formAREAV1_0_Rest_Interruptions -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct RestInterruption { - pub name: FixedCharSlice<32>, - pub interruption_explanation_text: [u32; 10], - pub resref_of_creature_to_spawn: [u64; 10], + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, + #[br(count = 40)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub interruption_explanation_text: String, + #[br(count = 10)] + pub resref_of_creature_to_spawn: Vec, pub count_of_creatures_in_spawn_table: u16, pub difficulty: u16, pub removal_time: u32, @@ -681,5 +624,106 @@ pub struct RestInterruption { pub probability_day_per_hour: u16, pub probability_night_per_hour: u16, #[serde(skip)] - _unused: FixedCharSlice<56>, + #[br(count = 56)] + _unused: Vec, +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::{ + fs::File, + io::{BufReader, Read}, + }; + + #[test] + fn test_ambients() { + let file = File::open("fixtures/AR0011.ARE").expect("Fixture missing"); + let mut buffer = Vec::new(); + BufReader::new(file) + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let area: Area = Area::new(&buffer); + assert_eq!( + area.ambients[0].name, + "Main Ambient\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + assert_eq!(area.ambients[0].resref_of_sound_1.0, "AM0011\0\0"); + assert_eq!( + area.ambients[1].name, + "SS-wispers\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + } + + #[test] + fn test_projectile_traps() { + let file = File::open("fixtures/AR0002.ARE").expect("Fixture missing"); + let mut buffer = Vec::new(); + BufReader::new(file) + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let area: Area = Area::new(&buffer); + assert_eq!(area.projectile_traps, vec![]) + } + + #[test] + fn test_actors() { + let file = File::open("fixtures/AR0002.ARE").expect("Fixture missing"); + let mut buffer = Vec::new(); + BufReader::new(file) + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let area: Area = Area::new(&buffer); + assert_eq!( + area.actors[0], + Actor { + name: "Priest of Helm\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_string(), + current_x_coordinate: 446, + current_y_coordinate: 333, + destination_x_coordinate: 446, + destination_y_coordinate: 333, + flags: 1, + has_been_spawned: 0, + first_letter_of_cre_resref: 0, + _unused_1: 0, + actor_animation: 24576, + actor_orientation: 0, + _unused: 0, + actor_removal_timer: 4294967295, + movement_restriction_distance: 0, + movement_restriction_distance_move_to_object: 0, + actor_appearence_schedule: 4294967295, + num_times_talked_to: 0, + dialog: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_override: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_general: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_class: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_race: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_default: Resref("\0\0\0\0\0\0\0\0".to_string()), + script_specific: Resref("\0\0\0\0\0\0\0\0".to_string()), + cre_file: Resref("PRIHEL\0\0".to_string()), + offset_to_cre_structure: 0, + size_of_stored_cre_structure: 0, + _unused_2: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + } + ) + } + + #[test] + fn test_spawn_point() { + let file = File::open("fixtures/AR0226.ARE").expect("Fixture missing"); + let mut buffer = Vec::new(); + BufReader::new(file) + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let automapnotes = Area::new(&buffer).automap_notes; + assert_eq!(automapnotes, vec![]) + } } diff --git a/models/src/biff.rs b/models/src/biff.rs index 98917d9..6000afb 100644 --- a/models/src/biff.rs +++ b/models/src/biff.rs @@ -1,8 +1,8 @@ use core::mem::size_of; use std::{collections::HashMap, fmt::Debug, rc::Rc}; +use crate::common::fixed_char_array::FixedCharSlice; use crate::common::header::Header; -use crate::common::signed_fixed_char_array::SignedFixedCharSlice; use crate::resources::utils::{copy_buff_to_struct, copy_transmute_buff}; use crate::{from_buffer, model::Model, resources::types::ResourceType}; @@ -20,14 +20,14 @@ impl Biff { pub fn new(buffer: &[u8]) -> Self { let header = copy_buff_to_struct::(buffer, 0); - let start = usize::try_from(header.offset_to_file_entries).unwrap_or(0); - let count = usize::try_from(header.count_of_fileset_entries).unwrap_or(0); + let start = header.offset_to_file_entries as usize; + let count = header.count_of_fileset_entries as usize; let file_set = copy_transmute_buff::(buffer, start, count); let mut fileset_entries: HashMap> = HashMap::new(); for header in file_set { - let start = usize::try_from(header.offset).unwrap_or(0); - let end = start + usize::try_from(header.size).unwrap_or(0); + let start = header.offset as usize; + let end = start + header.size as usize; let buffer = buffer.get(start..end).unwrap(); if let Some(data) = from_buffer(buffer, header.resource_type) { fileset_entries @@ -45,13 +45,11 @@ impl Biff { } pub fn populate_tiles(&mut self, buffer: &[u8]) { - let start_of_file_entries = - usize::try_from(self.header.offset_to_file_entries).unwrap_or(0); - let count_of_file_entries = - usize::try_from(self.header.count_of_fileset_entries).unwrap_or(0); + let start_of_file_entries = self.header.offset_to_file_entries as usize; + let count_of_file_entries = self.header.count_of_fileset_entries as usize; let start = start_of_file_entries + count_of_file_entries * size_of::(); - let count = usize::try_from(self.header.count_of_tileset_entries).unwrap_or(0); + let count = self.header.count_of_tileset_entries as usize; self.tileset_entries = copy_transmute_buff::(buffer, start, count); } } @@ -70,7 +68,7 @@ pub struct BiffHeader { #[repr(C, packed)] #[derive(Debug, Copy, Clone)] pub struct FilesetEntryHeader { - pub resource_locator: SignedFixedCharSlice<4>, + pub resource_locator: FixedCharSlice<4>, pub offset: u32, pub size: u32, pub resource_type: ResourceType, diff --git a/models/src/bio.rs b/models/src/bio.rs index 88e74ee..08ccf87 100644 --- a/models/src/bio.rs +++ b/models/src/bio.rs @@ -1,16 +1,21 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::{common::variable_char_array::VariableCharArray, model::Model, tlk::Lookup}; +use crate::{model::Model, tlk::Lookup}; -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] -pub struct Biography(pub VariableCharArray); +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] +pub struct Biography( + #[bw(map = |x| x.parse::().unwrap())] + #[br(parse_with = binrw::helpers::until_eof, map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + pub String, +); impl Model for Biography { fn new(buffer: &[u8]) -> Self { - Self(VariableCharArray(buffer.into())) + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -22,6 +27,27 @@ impl Model for Biography { } fn to_bytes(&self) -> Vec { - self.0 .0.to_vec() + self.0.as_bytes().to_vec() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::{ + fs::File, + io::{BufReader, Read}, + }; + + #[test] + fn read_biography() { + let file = File::open("fixtures/test.bio").expect("Fixture missing"); + let mut buffer = Vec::new(); + BufReader::new(file) + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let bio = Biography::new(&buffer); + assert_eq!(bio.0, String::from_utf8(buffer).unwrap()); } } diff --git a/models/src/character.rs b/models/src/character.rs index ddc70a4..58d0714 100644 --- a/models/src/character.rs +++ b/models/src/character.rs @@ -1,15 +1,13 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_array::FixedCharSlice; -use crate::common::signed_fixed_char_array::SignedFixedCharSlice; -use crate::resources::utils::{copy_buff_to_struct, to_u8_slice}; +use crate::common::resref::Resref; use crate::tlk::Lookup; -use crate::{common::header::Header, creature::Creature, model::Model}; +use crate::{creature::Creature, model::Model}; -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ExpandedCharacter { pub character: BGCharacter, pub creature: Creature, @@ -17,17 +15,8 @@ pub struct ExpandedCharacter { impl Model for ExpandedCharacter { fn new(buffer: &[u8]) -> Self { - let character = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(character.offset_to_cre_structure).unwrap_or(0); - let end = start + usize::try_from(character.length_of_the_cre_structure).unwrap_or(0); - - let creature = Creature::new(buffer.get(start..end).unwrap()); - - Self { - character, - creature, - } + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -39,35 +28,43 @@ impl Model for ExpandedCharacter { } fn to_bytes(&self) -> Vec { - let mut out = vec![]; - out.extend(to_u8_slice(&self.character).to_vec()); - out.extend(self.creature.to_bytes()); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct BGCharacter { - pub header: Header<4, 4>, - pub name: FixedCharSlice<32>, - pub offset_to_cre_structure: i32, - pub length_of_the_cre_structure: i32, - pub index_into_slots_ids_for_quick_weapon_1: i16, - pub index_into_slots_ids_for_quick_weapon_2: i16, - pub index_into_slots_ids_for_quick_weapon_3: i16, - pub index_into_slots_ids_for_quick_weapon_4: i16, - pub show_quick_weapon_1: i16, - pub show_quick_weapon_2: i16, - pub show_quick_weapon_3: i16, - pub show_quick_weapon_4: i16, - pub quick_spell_1_resource: SignedFixedCharSlice<8>, - pub quick_spell_2_resource: SignedFixedCharSlice<8>, - pub quick_spell_3_resource: SignedFixedCharSlice<8>, - pub index_into_slot_ids_for_quick_item_1: i16, - pub index_into_slot_ids_for_quick_item_2: i16, - pub index_into_slot_ids_for_quick_item_3: i16, - pub show_quick_item_1: i16, - pub show_quick_item_2: i16, - pub show_quick_item_3: i16, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, + pub offset_to_cre_structure: u32, + pub length_of_the_cre_structure: u32, + pub index_into_slots_ids_for_quick_weapon_1: u16, + pub index_into_slots_ids_for_quick_weapon_2: u16, + pub index_into_slots_ids_for_quick_weapon_3: u16, + pub index_into_slots_ids_for_quick_weapon_4: u16, + pub show_quick_weapon_1: u16, + pub show_quick_weapon_2: u16, + pub show_quick_weapon_3: u16, + pub show_quick_weapon_4: u16, + pub quick_spell_1_resource: Resref, + pub quick_spell_2_resource: Resref, + pub quick_spell_3_resource: Resref, + pub index_into_slot_ids_for_quick_item_1: u16, + pub index_into_slot_ids_for_quick_item_2: u16, + pub index_into_slot_ids_for_quick_item_3: u16, + pub show_quick_item_1: u16, + pub show_quick_item_2: u16, + pub show_quick_item_3: u16, } diff --git a/models/src/common/feature_block.rs b/models/src/common/feature_block.rs index d31c81e..eb186b6 100644 --- a/models/src/common/feature_block.rs +++ b/models/src/common/feature_block.rs @@ -1,9 +1,9 @@ +use binrw::{BinRead, BinWrite}; use serde::{Deserialize, Serialize}; -use super::fixed_char_array::FixedCharSlice; +use super::resref::Resref; -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct FeatureBlock { pub opcode_number: u16, pub target_type: u8, @@ -15,10 +15,11 @@ pub struct FeatureBlock { pub duration: u32, pub probability_1: u8, pub probability_2: u8, - pub resource: FixedCharSlice<8>, + pub resource: Resref, pub dice_thrown_max_level: u32, pub dice_sides_min_level: u32, - pub saving_throw_type: FixedCharSlice<4>, + #[br(count = 4)] + pub saving_throw_type: Vec, pub saving_throw_bonus: u32, pub stacking_id: u32, } diff --git a/models/src/common/fixed_char_array.rs b/models/src/common/fixed_char_array.rs index bf953a1..5e93fbf 100644 --- a/models/src/common/fixed_char_array.rs +++ b/models/src/common/fixed_char_array.rs @@ -37,9 +37,7 @@ impl Display for FixedCharSlice<{ N }> { write!( f, "{}", - std::str::from_utf8(&{ self.0 }) - .unwrap_or_default() - .replace('\0', "") + std::str::from_utf8(&{ self.0 }).unwrap_or_default() ) } } diff --git a/models/src/common/fixed_char_nd_array.rs b/models/src/common/fixed_char_nd_array.rs deleted file mode 100644 index cd397a9..0000000 --- a/models/src/common/fixed_char_nd_array.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::fmt::{Debug, Display}; - -use serde::{de::Visitor, ser::SerializeSeq, Deserialize, Serialize, Serializer}; - -use super::fixed_char_array::FixedCharSlice; - -#[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone)] -pub struct FixedCharNDArray(pub [FixedCharSlice; M]); - -impl Default for FixedCharNDArray { - fn default() -> Self { - Self([FixedCharSlice::::default(); M]) - } -} - -impl From<&[u8]> for FixedCharNDArray { - fn from(value: &[u8]) -> Self { - let mut destination = Self::default(); - for (counter, byte) in value.iter().enumerate() { - if counter >= N * M { - // TODO: Throw a warning here - break; - } - destination.0[counter / N].0[counter % N] = *byte; - } - destination - } -} - -impl Display for FixedCharNDArray { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - { self.0 } - .iter() - .map(|array| format!("{}", array)) - .reduce(|a, b| format!("{}, {}", a, b)) - .unwrap_or_default() - .replace('\0', "") - ) - } -} - -impl Debug for FixedCharNDArray { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("\"{}\"", self)) - } -} - -impl Serialize for FixedCharNDArray { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // TODO: Fix this crap - let mut seq = serializer.serialize_seq(Some({ self.0 }.len())).unwrap(); - for char_slice in self.0 { - let _ = seq.serialize_element(&char_slice); - } - seq.end() - } -} - -struct FixedCharNDArrayVisitor; - -impl<'de, const N: usize, const M: usize> Visitor<'de> for FixedCharNDArrayVisitor { - type Value = FixedCharNDArray; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "struct FixedCharNDArray") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut destination = FixedCharNDArray::::default(); - let mut counter = 0; - while let Ok(Some(item)) = seq.next_element::>() { - destination.0[counter] = item; - counter += 1; - } - Ok(destination) - } -} - -impl<'de, const N: usize, const M: usize> Deserialize<'de> for FixedCharNDArray { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_seq(FixedCharNDArrayVisitor) - } -} - -#[cfg(test)] -mod tests { - - use std::io::{BufReader, Read, Seek, SeekFrom, Write}; - - use super::*; - #[test] - fn valid_from_bytes() { - let from = "BALDUR".as_bytes(); - assert_eq!(FixedCharNDArray::<6, 1>::from(from).to_string(), "BALDUR") - } - - #[test] - fn valid_longer_from_bytes() { - let from = "BALDUR".as_bytes(); - assert_eq!(FixedCharNDArray::<7, 1>::from(from).to_string(), "BALDUR") - } - - #[test] - fn valid_shorter_from_bytes() { - let from = "BALDUR".as_bytes(); - assert_eq!(FixedCharNDArray::<5, 1>::from(from).to_string(), "BALDU") - } - - #[test] - fn valid_from_bytes_2d() { - let from = "BALDURBALDUR".as_bytes(); - assert_eq!( - FixedCharNDArray::<6, 2>::from(from).to_string(), - "BALDUR, BALDUR" - ) - } - - #[test] - fn valid_longer_from_bytes_3d() { - let from = "BALDURBALDURBALDUR".as_bytes(); - assert_eq!( - FixedCharNDArray::<7, 3>::from(from).to_string(), - "BALDURB, ALDURBA, LDUR" - ) - } - - #[test] - fn valid_shorter_from_bytes_4d() { - let from = "BALDURBALDURBALDURBALDURBALDUR".as_bytes(); - assert_eq!( - FixedCharNDArray::<5, 4>::from(from).to_string(), - "BALDU, RBALD, URBAL, DURBA" - ) - } - - #[test] - fn deserialize_serialize_deserialize() { - let from = "BALDUR".as_bytes(); - let expected = FixedCharNDArray::<3, 2>::from(from); - let value = serde_json::to_string(&expected).unwrap(); - - let mut file = tempfile::tempfile().unwrap(); - file.write_all(value.as_bytes()).unwrap(); - - file.seek(SeekFrom::Start(0)).unwrap(); - let mut buffer = Vec::new(); - let mut reader = BufReader::new(file); - reader - .read_to_end(&mut buffer) - .expect("Could not read to buffer"); - - let result: FixedCharNDArray<3, 2> = serde_json::from_slice(&buffer).unwrap(); - assert_eq!(expected, result) - } -} diff --git a/models/src/common/mod.rs b/models/src/common/mod.rs index 0078073..b9c0d61 100644 --- a/models/src/common/mod.rs +++ b/models/src/common/mod.rs @@ -1,6 +1,6 @@ pub mod feature_block; pub mod fixed_char_array; -pub mod fixed_char_nd_array; pub mod header; -pub mod signed_fixed_char_array; +pub mod resref; +pub mod strref; pub mod variable_char_array; diff --git a/models/src/common/resref.rs b/models/src/common/resref.rs new file mode 100644 index 0000000..8b9e95f --- /dev/null +++ b/models/src/common/resref.rs @@ -0,0 +1,10 @@ +use binrw::{BinRead, BinWrite}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, BinRead, BinWrite, Serialize, Deserialize)] +pub struct Resref( + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub String, +); diff --git a/models/src/common/signed_fixed_char_array.rs b/models/src/common/signed_fixed_char_array.rs deleted file mode 100644 index f7cddaf..0000000 --- a/models/src/common/signed_fixed_char_array.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - slice, -}; - -use serde::{de::Visitor, Deserialize, Serialize, Serializer}; - -#[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone)] -pub struct SignedFixedCharSlice(pub [i8; N]); - -impl Default for SignedFixedCharSlice { - fn default() -> Self { - Self([0; N]) - } -} - -impl From<&[i8]> for SignedFixedCharSlice { - fn from(value: &[i8]) -> Self { - let mut destination = [0; N]; - for (counter, byte) in value.iter().enumerate() { - if counter >= destination.len() { - // TODO: Throw a warning here - break; - } - destination[counter] = i8::from_le(*byte); - } - Self(destination) - } -} - -impl From<&[u8]> for SignedFixedCharSlice { - fn from(value: &[u8]) -> Self { - let mut destination = [0; N]; - for (counter, byte) in value.iter().enumerate() { - if counter >= destination.len() { - // TODO: Throw a warning here - break; - } - destination[counter] = i8::try_from(*byte).unwrap_or(0); - } - Self(destination) - } -} - -impl From<&str> for SignedFixedCharSlice { - fn from(value: &str) -> Self { - Self::from(unsafe { - slice::from_raw_parts(value.as_bytes().as_ptr() as *const i8, value.len()) - }) - } -} - -impl Display for SignedFixedCharSlice<{ N }> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - std::str::from_utf8(unsafe { - slice::from_raw_parts({ self.0 }.as_ptr() as *const u8, { self.0 }.len()) - }) - .unwrap_or_default() - .replace('\0', "") - ) - } -} - -impl Debug for SignedFixedCharSlice<{ N }> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("\"{}\"", self)) - } -} - -impl Serialize for SignedFixedCharSlice { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(self) - } -} - -struct SignedFixedCharSliceVisitor; - -impl<'de, const N: usize> Visitor<'de> for SignedFixedCharSliceVisitor { - type Value = SignedFixedCharSlice; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "struct SignedFixedCharSlice") - } - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Ok(SignedFixedCharSlice::from(v)) - } -} - -impl<'de, const N: usize> Deserialize<'de> for SignedFixedCharSlice { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(SignedFixedCharSliceVisitor) - } -} - -#[cfg(test)] -mod tests { - - use std::io::{BufReader, Read, Seek, SeekFrom, Write}; - - use super::*; - #[test] - fn valid_from_bytes_to_fixed_char_slice() { - let from = "BALDUR"; - assert_eq!( - SignedFixedCharSlice::<6>::try_from(from) - .unwrap_or_default() - .to_string(), - from - ) - } - - #[test] - fn valid_longer_from_bytes_to_fixed_char_slice() { - let from = "BALDUR"; - assert_eq!( - SignedFixedCharSlice::<7>::try_from(from) - .unwrap_or_default() - .to_string(), - "BALDUR" - ) - } - - #[test] - fn valid_shorter_from_bytes_to_fixed_char_slice() { - let from = "BALDUR"; - assert_eq!( - SignedFixedCharSlice::<5>::try_from(from) - .unwrap_or_default() - .to_string(), - "BALDU" - ) - } - - #[test] - fn deserialize_serialize_deserialize() { - let expected = SignedFixedCharSlice::<6>::from("BALDUR"); - let value = serde_json::to_string(&expected).unwrap(); - - let mut file = tempfile::tempfile().unwrap(); - file.write_all(value.as_bytes()).unwrap(); - - file.seek(SeekFrom::Start(0)).unwrap(); - let mut buffer = vec![]; - let mut reader = BufReader::new(file); - reader - .read_to_end(&mut buffer) - .expect("Could not read to buffer"); - - let result: SignedFixedCharSlice<6> = serde_json::from_slice(&buffer).unwrap(); - assert_eq!(expected, result) - } -} diff --git a/models/src/common/strref.rs b/models/src/common/strref.rs new file mode 100644 index 0000000..18c8442 --- /dev/null +++ b/models/src/common/strref.rs @@ -0,0 +1,5 @@ +use binrw::{BinRead, BinWrite}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] +pub struct Strref(pub u32); diff --git a/models/src/common/variable_char_array.rs b/models/src/common/variable_char_array.rs index 865410e..5c8548f 100644 --- a/models/src/common/variable_char_array.rs +++ b/models/src/common/variable_char_array.rs @@ -10,13 +10,7 @@ pub struct VariableCharArray(pub Rc<[u8]>); impl Display for VariableCharArray { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - std::str::from_utf8(&self.0) - .unwrap_or_default() - .replace('\0', "") - ) + write!(f, "{}", std::str::from_utf8(&self.0).unwrap_or_default()) } } @@ -75,16 +69,8 @@ impl<'de> Deserialize<'de> for VariableCharArray { #[cfg(test)] mod tests { - - use std::io::{BufReader, Read, Seek, SeekFrom, Write}; - use super::*; - #[test] - fn strips_nulls_and_returns() { - let input = "BALDUR\0"; - let expected = "BALDUR"; - assert_eq!(VariableCharArray::from(input).to_string(), expected) - } + use std::io::{BufReader, Read, Seek, SeekFrom, Write}; #[test] fn deserialize_serialize_deserialize() { diff --git a/models/src/creature.rs b/models/src/creature.rs index c859717..33e0cc4 100644 --- a/models/src/creature.rs +++ b/models/src/creature.rs @@ -1,74 +1,57 @@ use std::rc::Rc; +use binrw::{ + io::{Cursor, SeekFrom}, + BinRead, BinReaderExt, BinWrite, +}; use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_nd_array::FixedCharNDArray; -use crate::common::header::Header; -use crate::effect_v2::EffectV2WithOutSubHeader; +use crate::common::resref::Resref; +use crate::effect_v1::EffectV1; use crate::item_table::ItemReferenceTable; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; use crate::tlk::Lookup; use crate::{ - common::fixed_char_array::FixedCharSlice, item_table::ItemSlots, model::Model, spell_table::{KnownSpells, SpellMemorizationInfo, SpellMemorizationTable}, }; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm -#[repr(C)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct Creature { #[serde(flatten)] - pub header: BGEECreature, + pub header: BGEECreatureHeader, + #[serde(flatten)] + #[br(count=header.count_of_known_spells, seek_before=SeekFrom::Start(header.offset_to_known_spells as u64))] pub known_spells: Vec, - pub memorized_spell_info: Vec, + #[serde(flatten)] + #[br(count=header.count_of_memorized_spell_table, seek_before=SeekFrom::Start(header.offset_to_memorized_spell_table as u64))] pub memorized_spells: Vec, - pub effects: Vec, + #[serde(flatten)] + #[br(count=header.count_of_spell_memorization_info, seek_before=SeekFrom::Start(header.offset_to_spell_memorization_info as u64))] + pub memorized_spell_info: Vec, + #[serde(flatten)] + #[br(count=header.count_of_effects, seek_before=SeekFrom::Start(header.offset_to_effects as u64))] + pub effects: Vec, + #[serde(flatten)] + #[br(count=header.count_of_items, seek_before=SeekFrom::Start(header.offset_to_items as u64))] pub item_table: Vec, - pub item_slots: ItemSlots, + #[serde(flatten)] + #[br(seek_before=SeekFrom::Start(header.offset_to_item_slots as u64))] + pub item_slots: Option, } impl Model for Creature { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_known_spells).unwrap_or(0); - let count = usize::try_from(header.count_of_known_spells).unwrap_or(0); - let known_spells = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_spell_memorization_info).unwrap_or(0); - let count = usize::try_from(header.count_of_spell_memorization_info).unwrap_or(0); - let memorized_spell_info = - copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_memorized_spell_table).unwrap_or(0); - let count = usize::try_from(header.count_of_memorized_spell_table).unwrap_or(0); - let memorized_spells = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_effects).unwrap_or(0); - let count = usize::try_from(header.count_of_effects).unwrap_or(0); - let effects = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_items).unwrap_or(0); - let count = usize::try_from(header.count_of_items).unwrap_or(0); - let item_table = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_item_slots).unwrap_or(0); - let item_slots = copy_buff_to_struct::(buffer, start); - - Creature { - header, - known_spells, - memorized_spell_info, - memorized_spells, - effects, - item_table, - item_slots, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } + fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } @@ -78,29 +61,27 @@ impl Model for Creature { } fn to_bytes(&self) -> Vec { - let mut out = vec![]; - out.extend(to_u8_slice(&self.header).to_vec()); - out.extend(vec_to_u8_slice(&self.known_spells)); - out.extend(vec_to_u8_slice(&self.memorized_spell_info)); - out.extend(vec_to_u8_slice(&self.memorized_spells)); - out.extend(vec_to_u8_slice(&self.effects)); - out.extend(vec_to_u8_slice(&self.item_table)); - out.extend(to_u8_slice(&self.item_slots).to_vec()); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub struct BGEECreature { - pub header: Header<4, 4>, +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] +pub struct BGEECreatureHeader { + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, pub long_creature_name: u32, pub short_creature_name: u32, - // see CRE_FLAG_* above for possible flags pub flags: u32, - pub exp_for_killing: u32, pub exp: u32, pub gold: u32, @@ -108,7 +89,6 @@ pub struct BGEECreature { pub current_hp: u16, pub base_hp: u16, pub animation_id: u32, - pub metal_color: u8, pub minor_color: u8, pub major_color: u8, @@ -116,20 +96,18 @@ pub struct BGEECreature { pub leather_color: u8, pub armor_color: u8, pub hair_color: u8, - // 0 = v1, 1 = v2 pub effstructure: u8, - - pub small_portrait: FixedCharSlice<8>, - pub large_portrait: FixedCharSlice<8>, + pub small_portrait: Resref, + pub large_portrait: Resref, pub reputation: u8, pub hide_in_shadows: u8, - pub nac_1: i16, - pub nac_2: i16, - pub nac_mod_crushing: i16, - pub nac_mod_missile: i16, - pub nac_mod_piercing: i16, - pub nac_mod_slashing: i16, + pub nac_1: u16, + pub nac_2: u16, + pub nac_mod_crushing: u16, + pub nac_mod_missile: u16, + pub nac_mod_piercing: u16, + pub nac_mod_slashing: u16, pub thac0: u8, pub attacks: u8, pub save_death: u8, @@ -148,7 +126,6 @@ pub struct BGEECreature { pub resist_crushing: u8, pub resist_piercing: u8, pub resist_missile: u8, - pub detect_illusions: u8, pub set_traps: u8, pub lore: u8, @@ -156,20 +133,21 @@ pub struct BGEECreature { pub move_silently: u8, pub find_traps: u8, pub pick_pockets: u8, - pub fatique: u8, + pub fatigue: u8, pub intoxication: u8, pub luck: u8, - - pub proficiency_largeswords: u8, - pub proficiency_smallswords: u8, + pub proficiency_large_swords: u8, + pub proficiency_small_swords: u8, pub proficiency_bows: u8, pub proficiency_spears: u8, pub proficiency_blunt: u8, pub proficiency_spiked: u8, pub proficiency_axes: u8, pub proficiency_missiles: u8, - pub unused_proficencies: FixedCharSlice<7>, - + #[br(count = 7)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub unused_proficiencies: String, pub nightmare_mode: u8, pub translucency: u8, pub reputation_loss_if_killed: u8, @@ -178,19 +156,20 @@ pub struct BGEECreature { pub turn_undead_level: u8, pub tracking_skill: u8, // The following entry applies to BG1, BG2 and BGEE - pub tracking: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub tracking: String, // Strrefs pertaining to the character. // Most are connected with the sound-set (see SOUNDOFF.IDS (BG1) and SNDSLOT.IDS for (BG2)). // This is broken, it should be 100 u32s - pub strrefs: FixedCharNDArray<4, 100>, - + #[br(count = 100)] + pub strrefs: Vec, pub level_first_class: u8, pub level_second_class: u8, pub level_third_class: u8, - // from gender.ids via sex stat pub sex: u8, - pub strength: u8, pub strength_bonus: u8, pub intelligence: u8, @@ -201,52 +180,69 @@ pub struct BGEECreature { pub morale: u8, pub morale_break: u8, pub racial_enemy: u8, - pub morale_recovery_time: u16, - pub kit: u32, - pub override_script: FixedCharSlice<8>, - pub class_script: FixedCharSlice<8>, - pub race_script: FixedCharSlice<8>, - pub general_script: FixedCharSlice<8>, - pub default_script: FixedCharSlice<8>, - + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub override_script: String, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub class_script: String, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub race_script: String, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub general_script: String, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub default_script: String, pub enemy_ally: u8, pub general: u8, pub race: u8, pub class: u8, pub specific: u8, pub gender: u8, - // object.ids references - pub object_references: FixedCharSlice<5>, - + #[br(count = 5)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub object_references: String, pub alignment: u8, - pub global_actor_enumeration: u16, pub local_actor_enumeration: u16, - // death variable: sprite_is_dead on death - pub death_variable: FixedCharSlice<32>, - pub offset_to_known_spells: i32, - pub count_of_known_spells: i32, - pub offset_to_spell_memorization_info: i32, - pub count_of_spell_memorization_info: i32, - pub offset_to_memorized_spell_table: i32, - pub count_of_memorized_spell_table: i32, - pub offset_to_item_slots: i32, - pub offset_to_items: i32, - pub count_of_items: i32, - pub offset_to_effects: i32, - pub count_of_effects: i32, - - pub dialog_ref: FixedCharSlice<8>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub death_variable: String, + pub offset_to_known_spells: u32, + pub count_of_known_spells: u32, + pub offset_to_spell_memorization_info: u32, + pub count_of_spell_memorization_info: u32, + pub offset_to_memorized_spell_table: u32, + pub count_of_memorized_spell_table: u32, + pub offset_to_item_slots: u32, + pub offset_to_items: u32, + pub count_of_items: u32, + pub offset_to_effects: u32, + pub count_of_effects: u32, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub dialog_ref: String, } #[cfg(test)] mod tests { use super::*; + use binrw::{io::Cursor, BinReaderExt}; use std::{ fs::File, io::{BufReader, Read}, @@ -259,24 +255,26 @@ mod tests { BufReader::new(file) .read_to_end(&mut buffer) .expect("Could not read to buffer"); - - let creature = copy_buff_to_struct::(&buffer, 0); - assert_eq!({ creature.base_hp }, 8); - assert_eq!({ creature.level_first_class }, 1); - assert_eq!({ creature.level_second_class }, 1); - assert_eq!({ creature.level_third_class }, 1); - assert_eq!({ creature.sex }, 1); - assert_eq!({ creature.strength }, 9); - assert_eq!({ creature.strength_bonus }, 0); - assert_eq!({ creature.intelligence }, 9); - assert_eq!({ creature.wisdom }, 9); - assert_eq!({ creature.dexterity }, 9); - assert_eq!({ creature.constitution }, 9); - assert_eq!({ creature.charisma }, 9); - assert_eq!({ creature.morale }, 10); - assert_eq!({ creature.offset_to_item_slots }, 996); - assert_eq!({ creature.offset_to_memorized_spell_table }, 996); - assert_eq!(creature.dialog_ref, "dbeggar\0".into()); + let mut reader = Cursor::new(&buffer); + let header: BGEECreatureHeader = reader.read_le().unwrap(); + assert_eq!(header.signature, "CRE ".to_string()); + assert_eq!(header.version, "V1.0".to_string()); + assert_eq!(header.base_hp, 8); + assert_eq!(header.level_first_class, 1); + assert_eq!(header.level_second_class, 1); + assert_eq!(header.level_third_class, 1); + assert_eq!(header.sex, 1); + assert_eq!(header.strength, 9); + assert_eq!(header.strength_bonus, 0); + assert_eq!(header.intelligence, 9); + assert_eq!(header.wisdom, 9); + assert_eq!(header.dexterity, 9); + assert_eq!(header.constitution, 9); + assert_eq!(header.charisma, 9); + assert_eq!(header.morale, 10); + assert_eq!(header.offset_to_item_slots, 996); + assert_eq!(header.offset_to_memorized_spell_table, 996); + assert_eq!(header.dialog_ref, "dbeggar\0".to_string()); } #[test] @@ -287,23 +285,23 @@ mod tests { .read_to_end(&mut buffer) .expect("Could not read to buffer"); - let creature = copy_buff_to_struct::(&buffer, 0); - assert_eq!({ creature.base_hp }, 87); - assert_eq!({ creature.level_first_class }, 30); - assert_eq!({ creature.level_second_class }, 30); - assert_eq!({ creature.level_third_class }, 1); - assert_eq!({ creature.sex }, 2); - assert_eq!({ creature.strength }, 13); - assert_eq!({ creature.strength_bonus }, 0); - assert_eq!({ creature.intelligence }, 18); - assert_eq!({ creature.wisdom }, 17); - assert_eq!({ creature.dexterity }, 16); - assert_eq!({ creature.constitution }, 12); - assert_eq!({ creature.charisma }, 15); - assert_eq!({ creature.morale }, 10); - assert_eq!({ creature.offset_to_item_slots }, 2628); - assert_eq!({ creature.offset_to_memorized_spell_table }, 2100); - assert_eq!({ creature.offset_to_spell_memorization_info }, 1828); - assert_eq!(creature.dialog_ref, "None\0\0\0\0".into()); + let mut reader = Cursor::new(&buffer); + let creature: BGEECreatureHeader = reader.read_le().unwrap(); + assert_eq!(creature.base_hp, 87); + assert_eq!(creature.level_first_class, 30); + assert_eq!(creature.level_second_class, 30); + assert_eq!(creature.level_third_class, 1); + assert_eq!(creature.sex, 2); + assert_eq!(creature.strength, 13); + assert_eq!(creature.strength_bonus, 0); + assert_eq!(creature.intelligence, 18); + assert_eq!(creature.wisdom, 17); + assert_eq!(creature.dexterity, 16); + assert_eq!(creature.constitution, 12); + assert_eq!(creature.charisma, 15); + assert_eq!(creature.morale, 10); + assert_eq!(creature.offset_to_item_slots, 2628); + assert_eq!(creature.offset_to_memorized_spell_table, 2100); + assert_eq!(creature.offset_to_spell_memorization_info, 1828) } } diff --git a/models/src/dialogue.rs b/models/src/dialogue.rs index 6890245..80ff910 100644 --- a/models/src/dialogue.rs +++ b/models/src/dialogue.rs @@ -1,59 +1,42 @@ use std::rc::Rc; +use binrw::{ + io::{Cursor, SeekFrom}, + BinRead, BinReaderExt, BinWrite, +}; use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_array::FixedCharSlice; -use crate::common::header::Header; +use crate::common::resref::Resref; +use crate::common::strref::Strref; use crate::model::Model; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; use crate::tlk::Lookup; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Dialogue { + #[serde(flatten)] pub header: DialogueHeader, + #[serde(flatten)] + #[br(count=header.count_of_state_tables, seek_before=SeekFrom::Start(header.offset_to_state_table as u64))] pub state_tables: Vec, + #[serde(flatten)] + #[br(count=header.count_of_transitions, seek_before=SeekFrom::Start(header.offset_to_transition_table as u64))] pub transitions: Vec, + #[serde(flatten)] + #[br(count=header.count_of_state_triggers, seek_before=SeekFrom::Start(header.offset_to_state_trigger_table as u64))] pub state_triggers: Vec, + #[serde(flatten)] + #[br(count=header.count_of_transition_triggers, seek_before=SeekFrom::Start(header.offset_to_transition_trigger_table as u64))] pub transition_triggers: Vec, + #[serde(flatten)] + #[br(count=header.count_of_action_tables, seek_before=SeekFrom::Start(header.offset_to_action_table as u64))] pub action_tables: Vec, } impl Model for Dialogue { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_state_table).unwrap_or(0); - let count = usize::try_from(header.count_of_state_tables).unwrap_or(0); - let state_tables = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_transition_table).unwrap_or(0); - let count = usize::try_from(header.count_of_transitions).unwrap_or(0); - let transitions = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_state_trigger_table).unwrap_or(0); - let count = usize::try_from(header.count_of_state_triggers).unwrap_or(0); - let state_triggers = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_transition_trigger_table).unwrap_or(0); - let count = usize::try_from(header.count_of_transition_triggers).unwrap_or(0); - let transition_triggers = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_action_table).unwrap_or(0); - let count = usize::try_from(header.count_of_action_tables).unwrap_or(0); - let action_tables = copy_transmute_buff::(buffer, start, count); - - Self { - header, - state_tables, - transitions, - state_triggers, - transition_triggers, - action_tables, - } + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -65,76 +48,75 @@ impl Model for Dialogue { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.state_tables)); - out.extend(vec_to_u8_slice(&self.transitions)); - out.extend(vec_to_u8_slice(&self.state_triggers)); - out.extend(vec_to_u8_slice(&self.transition_triggers)); - out.extend(vec_to_u8_slice(&self.action_tables)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct DialogueHeader { - pub header: Header<4, 4>, - pub count_of_state_tables: i32, - pub offset_to_state_table: i32, - pub count_of_transitions: i32, - pub offset_to_transition_table: i32, - pub offset_to_state_trigger_table: i32, - pub count_of_state_triggers: i32, - pub offset_to_transition_trigger_table: i32, - pub count_of_transition_triggers: i32, - pub offset_to_action_table: i32, - pub count_of_action_tables: i32, - pub flags: FixedCharSlice<4>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, + pub count_of_state_tables: u32, + pub offset_to_state_table: u32, + pub count_of_transitions: u32, + pub offset_to_transition_table: u32, + pub offset_to_state_trigger_table: u32, + pub count_of_state_triggers: u32, + pub offset_to_transition_trigger_table: u32, + pub count_of_transition_triggers: u32, + pub offset_to_action_table: u32, + pub count_of_action_tables: u32, + #[br(count = 4)] + pub flags: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_State -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct StateTable { - pub actor_response_text: FixedCharSlice<4>, + pub actor_response_text: Strref, pub index_of_the_first_transition: u32, pub count_of_transitions: u32, pub index_of_state_trigger: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_Transition -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Transition { - pub flags: FixedCharSlice<4>, - pub player_character_text: FixedCharSlice<4>, - pub journal_text: FixedCharSlice<4>, + #[br(count = 4)] + pub flags: Vec, + pub player_character_text: Strref, + pub journal_text: Strref, pub index_of_transitions_trigger: u32, pub index_of_transitions_action_table: u32, - pub resource_name: FixedCharSlice<8>, + pub resource_name: Resref, pub index_of_the_next_state: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_StateTrigger -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct StateTrigger { pub offset_to_start_of_file: u32, pub length_in_bytes: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_TransTrigger -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct TransitionTrigger { pub offset_to_start_of_file: u32, pub length_in_bytes: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/dlg_v1.htm#formDLGV1_Action -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ActionTable { pub offset_to_start_of_file: u32, pub length_in_bytes: u32, diff --git a/models/src/effect_v1.rs b/models/src/effect_v1.rs index 0243c96..953259d 100644 --- a/models/src/effect_v1.rs +++ b/models/src/effect_v1.rs @@ -1,15 +1,14 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_array::FixedCharSlice; +use crate::common::resref::Resref; use crate::model::Model; -use crate::resources::utils::{copy_buff_to_struct, to_u8_slice}; use crate::tlk::Lookup; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/eff_v1.htm#effv1_Header -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct EffectV1 { pub effect_type: u16, pub target_type: u8, @@ -21,18 +20,19 @@ pub struct EffectV1 { pub duration: u32, pub probability_1: u8, pub probability_2: u8, - pub resref_key: FixedCharSlice<8>, + pub resref_key: Resref, pub dice_thrown_maximum_level: u32, - pub dice_sides_minimmum_level: u32, + pub dice_sides_minimum_level: u32, pub saving_throw_type: u32, pub saving_throw_bonus: u32, - #[serde(skip_serializing)] + #[serde(skip)] _unknown: u32, } impl Model for EffectV1 { fn new(buffer: &[u8]) -> Self { - copy_buff_to_struct::(buffer, 0) + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -44,6 +44,8 @@ impl Model for EffectV1 { } fn to_bytes(&self) -> Vec { - to_u8_slice(&self).to_vec() + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } diff --git a/models/src/effect_v2.rs b/models/src/effect_v2.rs index 950eb48..e0110ec 100644 --- a/models/src/effect_v2.rs +++ b/models/src/effect_v2.rs @@ -1,25 +1,29 @@ -use std::{mem::size_of, rc::Rc}; +use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::header::Header; -use crate::resources::utils::{copy_buff_to_struct, to_u8_slice}; +use crate::common::resref::Resref; +use crate::model::Model; use crate::tlk::Lookup; -use crate::{common::fixed_char_array::FixedCharSlice, model::Model}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/eff_v2.htm -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct EffectV2 { - pub header: Header<4, 4>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, #[serde(flatten)] pub body: EffectV2Body, } impl Model for EffectV2 { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::>(buffer, 0); - // There is one weird file in BG1, to do with Opcode 67 let tmp = if buffer.len() < 272 { let mut temp = buffer.to_vec(); temp.extend([0_u8]); @@ -27,8 +31,13 @@ impl Model for EffectV2 { } else { buffer.to_vec() }; - let body = copy_buff_to_struct::(&tmp, size_of::>()); - Self { header, body } + let mut reader = Cursor::new(tmp); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } + } } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -40,33 +49,29 @@ impl Model for EffectV2 { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(to_u8_slice(&self.body).to_vec()); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } -// https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_Effects -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct EffectV2WithOutSubHeader { - pub header: Header<4, 4>, - #[serde(flatten)] - pub body: EffectV2BodyWithOutHeader, -} - // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/eff_v2.htm#effv2_Body -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct EffectV2Body { - #[serde(flatten)] - pub header: Header<4, 4>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, #[serde(flatten)] pub body: EffectV2BodyWithOutHeader, } -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +// https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_Effects +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct EffectV2BodyWithOutHeader { pub opcode_number: u32, pub target_type: u32, @@ -78,7 +83,7 @@ pub struct EffectV2BodyWithOutHeader { pub duration: u32, pub probability_1: u16, pub probability_2: u16, - pub resource_1: FixedCharSlice<8>, + pub resource_1: Resref, pub dice_thrown: u32, pub dice_sides: u32, pub saving_throw_type: u32, @@ -94,41 +99,41 @@ pub struct EffectV2BodyWithOutHeader { pub parameter_4: u32, pub parameter_5: u32, pub time_applied_ticks: u32, - pub resource_2: FixedCharSlice<8>, - pub resource_3: FixedCharSlice<8>, - pub caster_x_coordinate: i32, - pub caster_y_coordinate: i32, - pub target_x_coordinate: i32, - pub target_y_coordinate: i32, + pub resource_2: Resref, + pub resource_3: Resref, + pub caster_x_coordinate: u32, + pub caster_y_coordinate: u32, + pub target_x_coordinate: u32, + pub target_y_coordinate: u32, pub parent_resource_type: u32, - pub parent_resource: FixedCharSlice<8>, - pub parent_resource_flags: FixedCharSlice<4>, + pub parent_resource: Resref, + #[br(count = 4)] + pub parent_resource_flags: Vec, pub projectile: u32, - pub parent_resource_slot: i32, - pub variable_name: FixedCharSlice<32>, + pub parent_resource_slot: u32, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub variable_name: String, pub caster_level: u32, pub first_apply: u32, // https://gibberlings3.github.io/iesdp/files/2da/2da_bgee/msectype.htm pub secondary_type: u32, #[serde(skip)] - _unknown_2: [u32; 15], + #[br(count = 15)] + _unknown_2: Vec, } #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; use std::{ fs::File, io::{BufReader, Read}, }; - #[test] - fn valid_sizes() { - assert_eq!(std::mem::size_of::(), 272); - assert_eq!(std::mem::size_of::(), 264) - } - #[test] fn valid_simple_creature_file_header_parsed() { let file = File::open("fixtures/#trollis.eff").unwrap(); @@ -142,15 +147,11 @@ mod tests { assert_eq!( EffectV2::new(&buffer), EffectV2 { - header: Header { - signature: "EFF ".into(), - version: "V2.0".into(), - }, + signature: "EFF ".to_string(), + version: "V2.0".to_string(), body: EffectV2Body { - header: Header { - signature: "EFF ".into(), - version: "V2.0".into(), - }, + signature: "EFF ".to_string(), + version: "V2.0".to_string(), body: EffectV2BodyWithOutHeader { opcode_number: 98, target_type: 2, @@ -162,7 +163,7 @@ mod tests { duration: 120, probability_1: 100, probability_2: 0, - resource_1: FixedCharSlice::default(), + resource_1: Resref("\0\0\0\0\0\0\0\0".to_string()), dice_thrown: 0, dice_sides: 0, saving_throw_type: 0, @@ -177,22 +178,24 @@ mod tests { parameter_4: 0, parameter_5: 0, time_applied_ticks: 0, - resource_2: FixedCharSlice::default(), - resource_3: FixedCharSlice::default(), - caster_x_coordinate: -1, - caster_y_coordinate: -1, - target_x_coordinate: -1, - target_y_coordinate: -1, + resource_2: Resref("\0\0\0\0\0\0\0\0".to_string()), + resource_3: Resref("\0\0\0\0\0\0\0\0".to_string()), + caster_x_coordinate: 4294967295, + caster_y_coordinate: 4294967295, + target_x_coordinate: 4294967295, + target_y_coordinate: 4294967295, parent_resource_type: 0, - parent_resource: FixedCharSlice::default(), - parent_resource_flags: FixedCharSlice::default(), + parent_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + parent_resource_flags: vec![0; 4], projectile: 0, - parent_resource_slot: -1, - variable_name: "".into(), + parent_resource_slot: 4294967295, + variable_name: + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + .into(), caster_level: 0, first_apply: 0, secondary_type: 0, - _unknown_2: [0; 15], + _unknown_2: vec![0; 15], } }, } diff --git a/models/src/game.rs b/models/src/game.rs index 6859bd9..f22b8cb 100644 --- a/models/src/game.rs +++ b/models/src/game.rs @@ -1,87 +1,51 @@ -use std::{mem::size_of, rc::Rc}; +use std::rc::Rc; +use binrw::{ + io::{Cursor, SeekFrom}, + BinRead, BinReaderExt, BinWrite, +}; use serde::{Deserialize, Serialize}; -use crate::common::header::Header; -use crate::common::signed_fixed_char_array::SignedFixedCharSlice; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; +use crate::common::{resref::Resref, strref::Strref}; +use crate::model::Model; use crate::tlk::Lookup; -use crate::{common::fixed_char_array::FixedCharSlice, creature::Creature, model::Model}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct Game { + #[serde(flatten)] pub header: BGEEGameHeader, - pub party_npcs: Vec, - pub non_party_npcs: Vec, + #[serde(flatten)] + #[br(count=header.count_of_npc_structs_for_party_members, seek_before=SeekFrom::Start(header.offset_to_npc_structs_for_party_members as u64))] + pub party_npcs: Vec, + #[serde(flatten)] + #[br(count=header.count_of_npc_structs_for_npcs, seek_before=SeekFrom::Start(header.offset_to_npc_structs_for_npcs as u64))] + pub non_party_npcs: Vec, + #[serde(flatten)] + #[br(count=header.count_of_global_namespace_variables, seek_before=SeekFrom::Start(header.offset_to_global_namespace_variables as u64))] pub global_variables: Vec, + #[serde(flatten)] + #[br(count=header.count_of_journal_entries, seek_before=SeekFrom::Start(header.offset_to_journal_entries as u64))] pub journal_entries: Vec, - pub familiar: Familiar, + #[br(seek_before=SeekFrom::Start(header.offset_to_familiar as u64))] + pub familiar: Option, + #[serde(flatten)] + #[br(count=header.count_of_stored_locations, seek_before=SeekFrom::Start(header.offset_to_stored_locations as u64))] pub stored_locations: Vec, + #[serde(flatten)] + #[br(count=header.count_of_pocket_plane_locations, seek_before=SeekFrom::Start(header.offset_to_pocket_plane_locations as u64))] pub pocket_plane_locations: Vec, + #[serde(flatten)] + #[br(if(familiar.is_some()), parse_with=binrw::helpers::until_eof, seek_before=SeekFrom::Start(header.offset_to_familiar_extra as u64))] pub familiar_extra: Vec, } impl Model for Game { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - // NPCs - let start: usize = - usize::try_from(header.offset_to_npc_structs_for_party_members).unwrap_or(0); - let count: usize = - usize::try_from(header.count_of_npc_structs_for_party_members).unwrap_or(0); - let party_npcs = generate_npcs(buffer, start, count); - let start: usize = usize::try_from(header.offset_to_npc_structs_for_npcs).unwrap_or(0); - let count: usize = usize::try_from(header.count_of_npc_structs_for_npcs).unwrap_or(0); - let non_party_npcs = generate_npcs(buffer, start, count); - - let start: usize = - usize::try_from(header.offset_to_global_namespace_variables).unwrap_or(0); - let count: usize = usize::try_from(header.count_of_global_namespace_variables).unwrap_or(0); - let global_varriables = copy_transmute_buff::(buffer, start, count); - - let start: usize = usize::try_from(header.offset_to_journal_entries).unwrap_or(0); - let count: usize = usize::try_from(header.count_of_journal_entries).unwrap_or(0); - let journal_entries = copy_transmute_buff::(buffer, start, count); - - // Familar - let start: usize = usize::try_from(header.offset_to_familar).unwrap_or(0); - let familiar = copy_buff_to_struct::(buffer, start); - - let familiar_extra = match usize::try_from(familiar.offset_to_familiar_resources) { - Ok(offset) if offset < buffer.len() => { - let start: usize = - usize::try_from(familiar.offset_to_familiar_resources).unwrap_or(0); - let count: usize = (buffer.len() - offset) / size_of::(); - copy_transmute_buff::(buffer, start, count) - } - _ => vec![], - }; - - let start: usize = usize::try_from(header.offset_to_stored_locations).unwrap_or(0); - let count: usize = party_npcs.len(); - let stored_locations = copy_transmute_buff::(buffer, start, count); - - let start: usize = usize::try_from(header.offset_to_pocket_plane_locations).unwrap_or(0); - let count: usize = party_npcs.len(); - let pocket_plane_locations = copy_transmute_buff::(buffer, start, count); - - Self { - header, - party_npcs, - non_party_npcs, - global_variables: global_varriables, - journal_entries, - familiar, - stored_locations, - pocket_plane_locations, - familiar_extra, - } + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } + fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } @@ -91,24 +55,23 @@ impl Model for Game { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.party_npcs)); - out.extend(vec_to_u8_slice(&self.non_party_npcs)); - out.extend(vec_to_u8_slice(&self.global_variables)); - out.extend(vec_to_u8_slice(&self.journal_entries)); - out.extend(to_u8_slice(&self.familiar)); - out.extend(vec_to_u8_slice(&self.stored_locations)); - out.extend(vec_to_u8_slice(&self.pocket_plane_locations)); - out.extend(vec_to_u8_slice(&self.familiar_extra)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Header -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct BGEEGameHeader { - pub header: Header<4, 4>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, pub game_time: u32, pub selected_formation: u16, pub formation_button_1: u16, @@ -131,122 +94,116 @@ pub struct BGEEGameHeader { bit8: Storm Increasing bits 9->15 unused */ - pub weather_bitfield: FixedCharSlice<2>, - pub offset_to_npc_structs_for_party_members: i32, - pub count_of_npc_structs_for_party_members: i32, - pub offset_to_party_inventory: i32, - pub count_of_party_inventory: i32, - pub offset_to_npc_structs_for_npcs: i32, - pub count_of_npc_structs_for_npcs: i32, - pub offset_to_global_namespace_variables: i32, - pub count_of_global_namespace_variables: i32, - pub main_area: FixedCharSlice<8>, - pub offset_to_familiar_extra: i32, - pub count_of_journal_entries: i32, - pub offset_to_journal_entries: i32, - pub party_reputation: i32, - pub current_area: FixedCharSlice<8>, - pub gui_flags: FixedCharSlice<4>, - pub loading_progress: FixedCharSlice<4>, - pub offset_to_familar: i32, - pub offset_to_stored_locations: i32, - pub count_of_stored_locations: i32, - pub game_time_real_seconds: i32, - pub offset_to_pocket_plane_locations: i32, - pub count_of_pocket_plane_locations: i32, + #[br(count = 2)] + pub weather_bitfield: Vec, + pub offset_to_npc_structs_for_party_members: u32, + pub count_of_npc_structs_for_party_members: u32, + pub offset_to_party_inventory: u32, + pub count_of_party_inventory: u32, + pub offset_to_npc_structs_for_npcs: u32, + pub count_of_npc_structs_for_npcs: u32, + pub offset_to_global_namespace_variables: u32, + pub count_of_global_namespace_variables: u32, + pub main_area: Resref, + pub offset_to_familiar_extra: u32, + pub count_of_journal_entries: u32, + pub offset_to_journal_entries: u32, + pub party_reputation: u32, + pub current_area: Resref, + #[br(count = 4)] + pub gui_flags: Vec, + #[br(count = 4)] + pub loading_progress: Vec, + pub offset_to_familiar: u32, + pub offset_to_stored_locations: u32, + pub count_of_stored_locations: u32, + pub game_time_real_seconds: u32, + pub offset_to_pocket_plane_locations: u32, + pub count_of_pocket_plane_locations: u32, // EE fields pub zoom_level: u32, - pub random_encounter_area: FixedCharSlice<8>, - pub current_world_map: FixedCharSlice<8>, + pub random_encounter_area: Resref, + pub current_world_map: Resref, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub current_campaign: String, pub familiar_owner: u32, - pub random_encounter_script: [u8; 20], -} - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Npc { - pub game_npc: GameNPC, - pub creature: Creature, -} - -fn generate_npcs(buffer: &[u8], start: usize, count: usize) -> Vec { - copy_transmute_buff::(buffer, start, count) - .iter() - .map(|game_npc| { - let start = game_npc.offset_to_cre_resource as usize; - let creature_buffer = buffer.get(start..).unwrap(); - let creature = Creature::new(creature_buffer); - - Npc { - game_npc: *game_npc, - creature, - } - }) - .collect() + #[br(count = 20)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub random_encounter_script: String, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_NPC -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct GameNPC { pub character_selection: u16, // x0-0x5 = player_x_fill, 0x_ffff = not in party pub party_order: u16, pub offset_to_cre_resource: u32, pub size_of_cre_resource: u32, - pub character_name: FixedCharSlice<8>, - pub character_orientation: i32, - pub characters_current_area: [i8; 8], - pub character_x_coordinate: i16, - pub character_y_coordinate: i16, - pub viewing_rectangle_x_coordinate: i16, - pub viewing_rectangle_y_coordinate: i16, - pub modal_action: i16, + #[br(count = 8)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub character_name: String, + pub character_orientation: u32, + pub characters_current_area: Resref, + pub character_x_coordinate: u16, + pub character_y_coordinate: u16, + pub viewing_rectangle_x_coordinate: u16, + pub viewing_rectangle_y_coordinate: u16, + pub modal_action: u16, pub happiness: u16, // all of the num times interacted are not used so we just group them pub num_times_interacted: [u32; 24], // (0x_ffff = none) - pub index_of_quick_weapon_1: i16, + pub index_of_quick_weapon_1: u16, // (0x_ffff = none) - pub index_of_quick_weapon_2: i16, + pub index_of_quick_weapon_2: u16, // (0x_ffff = none) - pub index_of_quick_weapon_3: i16, + pub index_of_quick_weapon_3: u16, // (0x_ffff = none) - pub index_of_quick_weapon_4: i16, + pub index_of_quick_weapon_4: u16, // (0/1/2 or -1 disabled) - pub quick_weapon_slot_1_ability: i16, + pub quick_weapon_slot_1_ability: u16, // (0/1/2 or -1 disabled) - pub quick_weapon_slot_2_ability: i16, + pub quick_weapon_slot_2_ability: u16, // (0/1/2 or -1 disabled) - pub quick_weapon_slot_3_ability: i16, + pub quick_weapon_slot_3_ability: u16, // (0/1/2 or -1 disabled) - pub quick_weapon_slot_4_ability: i16, - pub quick_spell_1_resouce: i64, - pub quick_spell_2_resouce: i64, - pub quick_spell_3_resouce: i64, + pub quick_weapon_slot_4_ability: u16, + pub quick_spell_1_resource: Resref, + pub quick_spell_2_resource: Resref, + pub quick_spell_3_resource: Resref, // (0x_ffff = none) - pub index_of_quick_item_1: i16, + pub index_of_quick_item_1: u16, // (0x_ffff = none) - pub index_of_quick_item_2: i16, + pub index_of_quick_item_2: u16, // (0x_ffff = none) - pub index_of_quick_item_3: i16, + pub index_of_quick_item_3: u16, // (0/1/2 or -1 disabled) - pub quick_item_slot_1_ability: i16, + pub quick_item_slot_1_ability: u16, // (0/1/2 or -1 disabled) - pub quick_item_slot_2_ability: i16, + pub quick_item_slot_2_ability: u16, // (0/1/2 or -1 disabled) - pub quick_item_slot_3_ability: i16, - pub name: FixedCharSlice<32>, - pub talk_count: i32, + pub quick_item_slot_3_ability: u16, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, + pub talk_count: u32, + #[serde(flatten)] pub character_kill_stats: CharacterKillStats, // filename prefix for voice set - pub voice_set: SignedFixedCharSlice<8>, + #[br(count = 8)] + pub voice_set: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Stats -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct CharacterKillStats { - pub most_powerful_vanquished_name: SignedFixedCharSlice<4>, + pub most_powerful_vanquished_name: Strref, pub most_powerful_vanquished_xp_reward: u32, // 1/15 seconds pub time_in_party: u32, @@ -257,22 +214,32 @@ pub struct CharacterKillStats { pub unused: u16, // changed to * pub first_letter_of_cre_resref: u8, - pub chapter_kills_xp_gained: i32, - pub chapter_kills_number: i32, - pub game_kills_xp_gained: i32, - pub game_kills_number: i32, - pub favourite_spells: [u64; 4], - pub favourite_spell_count: [u16; 4], - pub favourite_weapons: [u64; 4], + pub chapter_kills_xp_gained: u32, + pub chapter_kills_number: u32, + pub game_kills_xp_gained: u32, + pub game_kills_number: u32, + #[serde(flatten)] + #[br(count = 4)] + pub favourite_spells: Vec, + #[serde(flatten)] + #[br(count = 4)] + pub favourite_spell_count: Vec, + #[serde(flatten)] + #[br(count = 4)] + pub favourite_weapons: Vec, // time equipped in combat - 1/15 seconds - pub favourite_weapon_time: [u16; 4], + #[serde(flatten)] + #[br(count = 4)] + pub favourite_weapon_time: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Variable -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct GlobalVariables { - pub name: SignedFixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub name: String, /* bit 0: int bit 1: float @@ -281,19 +248,21 @@ pub struct GlobalVariables { bit 4: strref bit 5: dword */ - pub variable_type: i16, - pub resource_value: i16, - pub dword_value: i32, - pub int_value: i32, - pub double_value: i64, - pub script_name_value: SignedFixedCharSlice<32>, + pub variable_type: u16, + pub resource_value: u16, + pub dword_value: u32, + pub int_value: u32, + pub double_value: u64, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub script_name_value: String, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Journal -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct JournalEntries { - pub journal_text: FixedCharSlice<4>, + pub journal_text: Strref, // seconds pub time: u32, pub current_chapter_number: u8, @@ -311,50 +280,120 @@ pub struct JournalEntries { } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Familiar -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct Familiar { - pub lawful_good_familiar: FixedCharSlice<8>, - pub lawful_neutral_familiar: FixedCharSlice<8>, - pub lawful_evil_familiar: FixedCharSlice<8>, - pub neutral_good_familiar: FixedCharSlice<8>, - pub neutral_familiar: FixedCharSlice<8>, - pub neutral_evil_familiar: FixedCharSlice<8>, - pub chaotic_good_familiar: FixedCharSlice<8>, - pub chaotic_neutral_familiar: FixedCharSlice<8>, - pub chaotic_evil_familiar: FixedCharSlice<8>, - pub offset_to_familiar_resources: i32, - pub lg_familiar_spell_count: FixedCharSlice<9>, - pub ln_familiar_spell_count: FixedCharSlice<9>, - pub cg_familiar_spell_count: FixedCharSlice<9>, - pub ng_familiar_spell_count: FixedCharSlice<9>, - pub tn_familiar_spell_count: FixedCharSlice<9>, - pub ne_familiar_spell_count: FixedCharSlice<9>, - pub le_familiar_spell_count: FixedCharSlice<9>, - pub cn_familiar_spell_count: FixedCharSlice<9>, - pub ce_familiar_spell_count: FixedCharSlice<9>, + pub lawful_good_familiar: Resref, + pub lawful_neutral_familiar: Resref, + pub lawful_evil_familiar: Resref, + pub neutral_good_familiar: Resref, + pub neutral_familiar: Resref, + pub neutral_evil_familiar: Resref, + pub chaotic_good_familiar: Resref, + pub chaotic_neutral_familiar: Resref, + pub chaotic_evil_familiar: Resref, + pub offset_to_familiar_resources: u32, + pub ce_level_1_familiar_spell_count: u32, + pub ce_level_2_familiar_spell_count: u32, + pub ce_level_3_familiar_spell_count: u32, + pub ce_level_4_familiar_spell_count: u32, + pub ce_level_5_familiar_spell_count: u32, + pub ce_level_6_familiar_spell_count: u32, + pub ce_level_7_familiar_spell_count: u32, + pub ce_level_8_familiar_spell_count: u32, + pub ce_level_9_familiar_spell_count: u32, + pub cg_level_1_familiar_spell_count: u32, + pub cg_level_2_familiar_spell_count: u32, + pub cg_level_3_familiar_spell_count: u32, + pub cg_level_4_familiar_spell_count: u32, + pub cg_level_5_familiar_spell_count: u32, + pub cg_level_6_familiar_spell_count: u32, + pub cg_level_7_familiar_spell_count: u32, + pub cg_level_8_familiar_spell_count: u32, + pub cg_level_9_familiar_spell_count: u32, + pub cn_level_1_familiar_spell_count: u32, + pub cn_level_2_familiar_spell_count: u32, + pub cn_level_3_familiar_spell_count: u32, + pub cn_level_4_familiar_spell_count: u32, + pub cn_level_5_familiar_spell_count: u32, + pub cn_level_6_familiar_spell_count: u32, + pub cn_level_7_familiar_spell_count: u32, + pub cn_level_8_familiar_spell_count: u32, + pub cn_level_9_familiar_spell_count: u32, + pub le_level_1_familiar_spell_count: u32, + pub le_level_2_familiar_spell_count: u32, + pub le_level_3_familiar_spell_count: u32, + pub le_level_4_familiar_spell_count: u32, + pub le_level_5_familiar_spell_count: u32, + pub le_level_6_familiar_spell_count: u32, + pub le_level_7_familiar_spell_count: u32, + pub le_level_8_familiar_spell_count: u32, + pub le_level_9_familiar_spell_count: u32, + pub lg_level_1_familiar_spell_count: u32, + pub lg_level_2_familiar_spell_count: u32, + pub lg_level_3_familiar_spell_count: u32, + pub lg_level_4_familiar_spell_count: u32, + pub lg_level_5_familiar_spell_count: u32, + pub lg_level_6_familiar_spell_count: u32, + pub lg_level_7_familiar_spell_count: u32, + pub lg_level_8_familiar_spell_count: u32, + pub lg_level_9_familiar_spell_count: u32, + pub ln_level_1_familiar_spell_count: u32, + pub ln_level_2_familiar_spell_count: u32, + pub ln_level_3_familiar_spell_count: u32, + pub ln_level_4_familiar_spell_count: u32, + pub ln_level_5_familiar_spell_count: u32, + pub ln_level_6_familiar_spell_count: u32, + pub ln_level_7_familiar_spell_count: u32, + pub ln_level_8_familiar_spell_count: u32, + pub ln_level_9_familiar_spell_count: u32, + pub ne_level_1_familiar_spell_count: u32, + pub ne_level_2_familiar_spell_count: u32, + pub ne_level_3_familiar_spell_count: u32, + pub ne_level_4_familiar_spell_count: u32, + pub ne_level_5_familiar_spell_count: u32, + pub ne_level_6_familiar_spell_count: u32, + pub ne_level_7_familiar_spell_count: u32, + pub ne_level_8_familiar_spell_count: u32, + pub ne_level_9_familiar_spell_count: u32, + pub ng_level_1_familiar_spell_count: u32, + pub ng_level_2_familiar_spell_count: u32, + pub ng_level_3_familiar_spell_count: u32, + pub ng_level_4_familiar_spell_count: u32, + pub ng_level_5_familiar_spell_count: u32, + pub ng_level_6_familiar_spell_count: u32, + pub ng_level_7_familiar_spell_count: u32, + pub ng_level_8_familiar_spell_count: u32, + pub ng_level_9_familiar_spell_count: u32, + pub tn_level_1_familiar_spell_count: u32, + pub tn_level_2_familiar_spell_count: u32, + pub tn_level_3_familiar_spell_count: u32, + pub tn_level_4_familiar_spell_count: u32, + pub tn_level_5_familiar_spell_count: u32, + pub tn_level_6_familiar_spell_count: u32, + pub tn_level_7_familiar_spell_count: u32, + pub tn_level_8_familiar_spell_count: u32, + pub tn_level_9_familiar_spell_count: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_Stored // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_PocketPlane -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct Location { - pub area: [i8; 8], - pub x_coordinate: i16, - pub y_coordinate: i16, + pub area: Resref, + pub x_coordinate: u16, + pub y_coordinate: u16, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/gam_v2.0.htm#GAMEV2_0_FamiliarExtra -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct FamiliarExtra { - pub data: [i8; 8], + pub data: Resref, } #[cfg(test)] mod tests { use super::*; + use pretty_assertions::{assert_eq, assert_ne}; use std::{ fs::File, io::{BufReader, Read}, @@ -370,18 +409,18 @@ mod tests { .expect("Could not read to buffer"); let header = Game::new(&buffer).header; - assert_eq!(header.header.signature, "GAME".into()); - assert_eq!(header.header.version, "V2.0".into()); - assert_eq!({ header.party_gold }, 109741); - assert_eq!({ header.game_time }, 1664811); - assert_eq!({ header.count_of_journal_entries }, 188); - assert_eq!({ header.game_time_real_seconds }, 2774117); - assert_eq!({ header.zoom_level }, 58); - assert_eq!({ header.familiar_owner }, 0); + assert_eq!(header.signature, "GAME".to_string()); + assert_eq!(header.version, "V2.0".to_string()); + assert_eq!(header.party_gold, 109741); + assert_eq!(header.game_time, 1664811); + assert_eq!(header.count_of_journal_entries, 188); + assert_eq!(header.game_time_real_seconds, 2774117); + assert_eq!(header.zoom_level, 58); + assert_eq!(header.familiar_owner, 0); } #[test] - fn valid_party_parsed() { + fn valid_party_npc_parsed() { let file = File::open("fixtures/BG2EEBALDUR.gam").unwrap(); let mut reader = BufReader::new(file); let mut buffer = Vec::new(); @@ -391,140 +430,219 @@ mod tests { let game = Game::new(&buffer); let party = game.party_npcs; - assert_ne!(party.first(), None); - if let Some(party) = party.last() { - assert_eq!( - party.game_npc, - GameNPC { - character_selection: 0, - party_order: 4, - offset_to_cre_resource: 71292, - size_of_cre_resource: 17084, - character_name: "*ERIE6".into(), - character_orientation: 6, - characters_current_area: [65, 82, 48, 56, 48, 48, 0, 0], - character_x_coordinate: 1000, - character_y_coordinate: 366, - viewing_rectangle_x_coordinate: 366, - viewing_rectangle_y_coordinate: 81, - modal_action: 0, - happiness: 80, - num_times_interacted: [0; 24], - index_of_quick_weapon_1: 35, - index_of_quick_weapon_2: 36, - index_of_quick_weapon_3: 10, - index_of_quick_weapon_4: 10, - quick_weapon_slot_1_ability: 0, - quick_weapon_slot_2_ability: 0, - quick_weapon_slot_3_ability: 0, - quick_weapon_slot_4_ability: 0, - quick_spell_1_resouce: 14126745667457107, - quick_spell_2_resouce: 15536323869233235, - quick_spell_3_resouce: 0, - index_of_quick_item_1: 15, - index_of_quick_item_2: -1, - index_of_quick_item_3: -1, - quick_item_slot_1_ability: 0, - quick_item_slot_2_ability: -1, - quick_item_slot_3_ability: -1, - name: "".into(), - talk_count: 6, - character_kill_stats: CharacterKillStats { - most_powerful_vanquished_name: SignedFixedCharSlice([-125, -120, 0, 0]), - most_powerful_vanquished_xp_reward: 64000, - time_in_party: 8320583, - time_joined: 24563462, - party_member: 1, - unused: 0, - first_letter_of_cre_resref: 42, - chapter_kills_xp_gained: 1551, - chapter_kills_number: 5, - game_kills_xp_gained: 153155, - game_kills_number: 113, - favourite_spells: [ - 14127836589150291, - 15535232947540051, - 15815591383289939, - 15815599822688339 - ], - favourite_spell_count: [8, 1, 57, 33], - favourite_weapons: [ - 54083508262210, - 21472643031384407, - 1414744390, - 20070800066560343 - ], - favourite_weapon_time: [1910, 126, 1716, 11844] - }, - voice_set: SignedFixedCharSlice::default(), - } - ); - } else { - panic!(); - } + assert_eq!( + *party.first().unwrap(), + GameNPC { + character_selection: 0, + party_order: 0, + offset_to_cre_resource: 2292, + size_of_cre_resource: 19752, + character_name: "*AV_PALA".to_string(), + character_orientation: 6, + characters_current_area: Resref("AR0800\0\0".to_string()), + character_x_coordinate: 968, + character_y_coordinate: 318, + viewing_rectangle_x_coordinate: 366, + viewing_rectangle_y_coordinate: 81, + modal_action: 0, + happiness: 80, + num_times_interacted: [0; 24], + index_of_quick_weapon_1: 35, + index_of_quick_weapon_2: 36, + index_of_quick_weapon_3: 37, + index_of_quick_weapon_4: 10, + quick_weapon_slot_1_ability: 0, + quick_weapon_slot_2_ability: 0, + quick_weapon_slot_3_ability: 0, + quick_weapon_slot_4_ability: 0, + quick_spell_1_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + quick_spell_2_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + quick_spell_3_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + index_of_quick_item_1: 15, + index_of_quick_item_2: 16, + index_of_quick_item_3: 17, + quick_item_slot_1_ability: 0, + quick_item_slot_2_ability: 0, + quick_item_slot_3_ability: 0, + name: "Nimi Iluvia\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_string(), + talk_count: 0, + character_kill_stats: CharacterKillStats { + most_powerful_vanquished_name: Strref(14430), + most_powerful_vanquished_xp_reward: 25000, + time_in_party: 0, + time_joined: 31499, + party_member: 1, + unused: 0, + first_letter_of_cre_resref: 42, + chapter_kills_xp_gained: 109150, + chapter_kills_number: 27, + game_kills_xp_gained: 2111235, + game_kills_number: 701, + favourite_spells: vec![ + Resref("SPCL211\0".to_string()), + Resref("SPPR111\0".to_string()), + Resref("SPIN101\0".to_string()), + Resref("SPIN103\0".to_string()) + ], + favourite_spell_count: vec![3, 660, 294, 76], + favourite_weapons: vec![ + Resref("SW1H62\0\0".to_string()), + Resref("SW1H24\0\0".to_string()), + Resref("SW1H25\0\0".to_string()), + Resref("SW1H51\0\0".to_string()) + ], + favourite_weapon_time: vec![200, 14644, 13948, 49692] + }, + voice_set: vec![0, 0, 0, 0, 0, 0, 0, 0] + } + ); + assert_eq!( + *party.last().unwrap(), + GameNPC { + character_selection: 0, + party_order: 4, + offset_to_cre_resource: 71292, + size_of_cre_resource: 17084, + character_name: "*ERIE6\0\0".to_string(), + character_orientation: 6, + characters_current_area: Resref("AR0800\0\0".to_string()), + character_x_coordinate: 1000, + character_y_coordinate: 366, + viewing_rectangle_x_coordinate: 366, + viewing_rectangle_y_coordinate: 81, + modal_action: 0, + happiness: 80, + num_times_interacted: [0; 24], + index_of_quick_weapon_1: 35, + index_of_quick_weapon_2: 36, + index_of_quick_weapon_3: 10, + index_of_quick_weapon_4: 10, + quick_weapon_slot_1_ability: 0, + quick_weapon_slot_2_ability: 0, + quick_weapon_slot_3_ability: 0, + quick_weapon_slot_4_ability: 0, + quick_spell_1_resource: Resref("SPWI302\0".to_string()), + quick_spell_2_resource: Resref("SPWI427\0".to_string()), + quick_spell_3_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + index_of_quick_item_1: 15, + index_of_quick_item_2: 65535, + index_of_quick_item_3: 65535, + quick_item_slot_1_ability: 0, + quick_item_slot_2_ability: 65535, + quick_item_slot_3_ability: 65535, + name: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".into(), + talk_count: 6, + character_kill_stats: CharacterKillStats { + most_powerful_vanquished_name: Strref(34947), + most_powerful_vanquished_xp_reward: 64000, + time_in_party: 8320583, + time_joined: 24563462, + party_member: 1, + unused: 0, + first_letter_of_cre_resref: 42, + chapter_kills_xp_gained: 1551, + chapter_kills_number: 5, + game_kills_xp_gained: 153155, + game_kills_number: 113, + favourite_spells: vec![ + Resref("SPWI112\0".to_string()), + Resref("SPWI617\0".to_string()), + Resref("SPPR208\0".to_string()), + Resref("SPWI408\0".to_string()) + ], + favourite_spell_count: vec![8, 1, 57, 33], + favourite_weapons: vec![ + Resref("BULL01\0\0".to_string()), + Resref("WAFLAIL\0".to_string()), + Resref("FIST\0\0\0\0".to_string()), + Resref("WASLING\0".to_string()) + ], + favourite_weapon_time: vec![1910, 126, 1716, 11844] + }, + voice_set: vec![0, 0, 0, 0, 0, 0, 0, 0], + } + ); + } + + #[test] + fn valid_npc_parsed() { + let file = File::open("fixtures/BG2EEBALDUR.gam").unwrap(); + let mut reader = BufReader::new(file); + let mut buffer = Vec::new(); + reader + .read_to_end(&mut buffer) + .expect("Could not read to buffer"); + let game = Game::new(&buffer); let non_party = game.non_party_npcs; assert_ne!(non_party.first(), None); - if let Some(non_party) = non_party.last() { - assert_eq!( - non_party.game_npc, - GameNPC { - character_selection: 0, - party_order: 65535, - offset_to_cre_resource: 196144, - size_of_cre_resource: 1868, - character_name: "*AZZY8".into(), - character_orientation: 14, - characters_current_area: [65, 82, 50, 48, 48, 50, 0, 0], - character_x_coordinate: 341, - character_y_coordinate: 400, - viewing_rectangle_x_coordinate: 0, - viewing_rectangle_y_coordinate: 0, - modal_action: 0, - happiness: 0, - num_times_interacted: [0; 24], - index_of_quick_weapon_1: 11, - index_of_quick_weapon_2: 36, - index_of_quick_weapon_3: 10, - index_of_quick_weapon_4: 10, - quick_weapon_slot_1_ability: 0, - quick_weapon_slot_2_ability: 0, - quick_weapon_slot_3_ability: 0, - quick_weapon_slot_4_ability: 0, - quick_spell_1_resouce: 0, - quick_spell_2_resouce: 0, - quick_spell_3_resouce: 0, - index_of_quick_item_1: -1, - index_of_quick_item_2: -1, - index_of_quick_item_3: -1, - quick_item_slot_1_ability: -1, - quick_item_slot_2_ability: -1, - quick_item_slot_3_ability: -1, - name: "".into(), - talk_count: 1, - character_kill_stats: CharacterKillStats { - most_powerful_vanquished_name: SignedFixedCharSlice([-1, -1, -1, -1]), - most_powerful_vanquished_xp_reward: 0, - time_in_party: 0, - time_joined: 0, - party_member: 0, - unused: 0, - first_letter_of_cre_resref: 42, - chapter_kills_xp_gained: 0, - chapter_kills_number: 0, - game_kills_xp_gained: 0, - game_kills_number: 0, - favourite_spells: [0, 0, 0, 0], - favourite_spell_count: [0, 0, 0, 0], - favourite_weapons: [0, 0, 0, 0], - favourite_weapon_time: [0, 0, 0, 0] - }, - voice_set: SignedFixedCharSlice::default(), - } - ); - } else { - panic!(); - } + assert_eq!( + *non_party.last().unwrap(), + GameNPC { + character_selection: 0, + party_order: 65535, + offset_to_cre_resource: 196144, + size_of_cre_resource: 1868, + character_name: "*AZZY8\0\0".into(), + character_orientation: 14, + characters_current_area: Resref("AR2002\0\0".to_string()), + character_x_coordinate: 341, + character_y_coordinate: 400, + viewing_rectangle_x_coordinate: 0, + viewing_rectangle_y_coordinate: 0, + modal_action: 0, + happiness: 0, + num_times_interacted: [0; 24], + index_of_quick_weapon_1: 11, + index_of_quick_weapon_2: 36, + index_of_quick_weapon_3: 10, + index_of_quick_weapon_4: 10, + quick_weapon_slot_1_ability: 0, + quick_weapon_slot_2_ability: 0, + quick_weapon_slot_3_ability: 0, + quick_weapon_slot_4_ability: 0, + quick_spell_1_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + quick_spell_2_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + quick_spell_3_resource: Resref("\0\0\0\0\0\0\0\0".to_string()), + index_of_quick_item_1: 65535, + index_of_quick_item_2: 65535, + index_of_quick_item_3: 65535, + quick_item_slot_1_ability: 65535, + quick_item_slot_2_ability: 65535, + quick_item_slot_3_ability: 65535, + name: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + .to_string(), + talk_count: 1, + character_kill_stats: CharacterKillStats { + most_powerful_vanquished_name: Strref(4294967295), + most_powerful_vanquished_xp_reward: 0, + time_in_party: 0, + time_joined: 0, + party_member: 0, + unused: 0, + first_letter_of_cre_resref: 42, + chapter_kills_xp_gained: 0, + chapter_kills_number: 0, + game_kills_xp_gained: 0, + game_kills_number: 0, + favourite_spells: vec![ + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()) + ], + favourite_spell_count: vec![0, 0, 0, 0], + favourite_weapons: vec![ + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()), + Resref("\0\0\0\0\0\0\0\0".to_string()) + ], + favourite_weapon_time: vec![0, 0, 0, 0] + }, + voice_set: vec![0, 0, 0, 0, 0, 0, 0, 0], + } + ) } #[test] @@ -536,17 +654,41 @@ mod tests { .read_to_end(&mut buffer) .expect("Could not read to buffer"); - let familiar = Game::new(&buffer).familiar; - assert_eq!(familiar.lawful_good_familiar, "FAMPSD".into()); - assert_eq!(familiar.lawful_neutral_familiar, "FAMFER".into()); - assert_eq!(familiar.lawful_evil_familiar, "FAMIMP".into()); - assert_eq!(familiar.neutral_good_familiar, "FAMPSD".into()); - assert_eq!(familiar.neutral_familiar, "FAMRAB".into()); - assert_eq!(familiar.neutral_evil_familiar, "FAMDUST".into()); - assert_eq!(familiar.chaotic_good_familiar, "FAMFAIR".into()); - assert_eq!(familiar.chaotic_neutral_familiar, "FAMCAT".into()); - assert_eq!(familiar.chaotic_evil_familiar, "FAMQUAS".into()); - assert_eq!({ familiar.offset_to_familiar_resources }, 318688); + let familiar = Game::new(&buffer).familiar.unwrap(); + assert_eq!( + familiar.lawful_good_familiar, + Resref("FAMPSD\0\0".to_string()) + ); + assert_eq!( + familiar.lawful_neutral_familiar, + Resref("FAMFER\0\0".to_string()) + ); + assert_eq!( + familiar.lawful_evil_familiar, + Resref("FAMIMP\0\0".to_string()) + ); + assert_eq!( + familiar.neutral_good_familiar, + Resref("FAMPSD\0\0".to_string()) + ); + assert_eq!(familiar.neutral_familiar, Resref("FAMRAB\0\0".to_string())); + assert_eq!( + familiar.neutral_evil_familiar, + Resref("FAMDUST\0".to_string()) + ); + assert_eq!( + familiar.chaotic_good_familiar, + Resref("FAMFAIR\0".to_string()) + ); + assert_eq!( + familiar.chaotic_neutral_familiar, + Resref("FAMCAT\0\0".to_string()) + ); + assert_eq!( + familiar.chaotic_evil_familiar, + Resref("FAMQUAS\0".to_string()) + ); + assert_eq!(familiar.offset_to_familiar_resources, 318688); } #[test] @@ -562,7 +704,7 @@ mod tests { assert_eq!( journal.first(), Some(&JournalEntries { - journal_text: FixedCharSlice([41, 133, 0, 0]), + journal_text: Strref(34089), time: 31595, current_chapter_number: 1, read_by_character: 255, @@ -573,7 +715,7 @@ mod tests { assert_eq!( journal.last(), Some(&JournalEntries { - journal_text: FixedCharSlice([63, 124, 1, 0]), + journal_text: Strref(97343), time: 24711890, current_chapter_number: 6, read_by_character: 255, diff --git a/models/src/ids.rs b/models/src/ids.rs index f784527..c881851 100644 --- a/models/src/ids.rs +++ b/models/src/ids.rs @@ -1,57 +1,22 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::{ - common::{ - fixed_char_array::FixedCharSlice, header::Header, variable_char_array::VariableCharArray, - }, - model::Model, - resources::utils::{row_parser, to_u8_slice, vec_to_u8_slice}, - tlk::Lookup, -}; +use crate::{model::Model, tlk::Lookup}; //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/ids.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Ids { - pub header: Header<3, 4>, - pub data_entries: Vec, + #[br(parse_with = binrw::helpers::until_eof, map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub data: String, } impl Model for Ids { fn new(buffer: &[u8]) -> Self { - let (headers, mut end) = row_parser(buffer, 0); - - let signature = if headers.first().is_some() { - FixedCharSlice::<3>::from(&buffer[0..3]) - } else { - FixedCharSlice::<3>::default() - }; - let version = match headers.last() { - Some(x) => FixedCharSlice::<4>::from(x.0.as_ref()), - _ => FixedCharSlice::<4>::from(signature.0.as_ref()), - }; - let header = Header { signature, version }; - - let mut data_entries = vec![]; - while end < buffer.len() { - let (row, row_end) = row_parser(buffer, end); - if !row.is_empty() { - data_entries.push(DataEntry { - value: row.first().unwrap().clone(), - identifier: row.last().unwrap().clone(), - }); - } - if end == row_end { - break; - } - end = row_end; - } - Ids { - header, - data_entries, - } + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -63,58 +28,8 @@ impl Model for Ids { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.data_entries)); - out - } -} - -#[repr(C)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct DataEntry { - pub value: VariableCharArray, - pub identifier: VariableCharArray, -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{ - fs::File, - io::{BufReader, Read}, - }; - - #[test] - fn valid_item_file_parsed() { - let file = File::open("fixtures/soundoff.ids").unwrap(); - let mut reader = BufReader::new(file); - let mut buffer = Vec::new(); - reader - .read_to_end(&mut buffer) - .expect("Could not read to buffer"); - let item = Ids::new(&buffer); - - assert_eq!( - item.header, - Header { - version: "V1.0".into(), - signature: "IDS".into(), - } - ); - - assert_eq!( - item.data_entries.first(), - Some(&DataEntry { - value: "0".into(), - identifier: "INITIAL_MEETING".into(), - }) - ); - assert_eq!( - item.data_entries.last(), - Some(&DataEntry { - value: "13".into(), - identifier: "BATTLE_CRY5".into(), - }) - ); + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } diff --git a/models/src/item.rs b/models/src/item.rs index 5cfce0d..76fa49d 100644 --- a/models/src/item.rs +++ b/models/src/item.rs @@ -1,94 +1,71 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; use crate::common::feature_block::FeatureBlock; -use crate::common::fixed_char_array::FixedCharSlice; -use crate::common::fixed_char_nd_array::FixedCharNDArray; -use crate::common::header::Header; +use crate::common::resref::Resref; use crate::model::Model; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; use crate::tlk::Lookup; //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/itm_v1.htm -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Item { + #[serde(flatten)] pub header: ItemHeader, + #[serde(flatten)] + #[br(count=header.count_of_extended_headers)] pub extended_headers: Vec, + #[serde(flatten)] + #[br(count=header.count_of_feature_blocks)] pub equipping_feature_blocks: Vec, } - impl Model for Item { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_extended_headers).unwrap_or(0); - let count = usize::try_from(header.count_of_extended_headers).unwrap_or(0); - let extended_headers = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_feature_blocks).unwrap_or(0); - let count = (buffer.len() - start) / std::mem::size_of::(); - let equipping_feature_blocks = - copy_transmute_buff::(buffer, start, count); - - Self { - header, - extended_headers, - equipping_feature_blocks, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } + fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } - fn name(&self, lookup: &Lookup) -> String { - let name = if self.header.identified_item_name > -1 - && self.header.identified_item_name < lookup.entries.len() as i32 - { - lookup - .strings - .get(self.header.identified_item_name as usize) - .unwrap() - .to_string() - } else if self.header.unidentified_item_name > -1 - && self.header.unidentified_item_name < lookup.entries.len() as i32 - { - lookup - .strings - .get(self.header.unidentified_item_name as usize) - .unwrap() - .to_string() - } else { - format!("{}", { self.header.identified_item_name }) - } - .to_ascii_lowercase() - .replace(' ', "_"); - format!("{}.itm", name) + fn name(&self, _lookup: &Lookup) -> String { + todo!() } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.extended_headers)); - out.extend(vec_to_u8_slice(&self.equipping_feature_blocks)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/itm_v1.htm#itmv1_Header -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ItemHeader { - header: Header<4, 4>, - unidentified_item_name: i32, - identified_item_name: i32, - replacement_item: FixedCharSlice<8>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + version: String, + unidentified_item_name: u32, + identified_item_name: u32, + replacement_item: Resref, // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/itm_v1.htm#Header_Flags type_flags: u32, category: u16, usability: u32, - item_animation: FixedCharSlice<2>, + #[br(count = 2)] + item_animation: Vec, min_level: u16, min_strength: u16, min_strength_bonus: u8, @@ -104,30 +81,29 @@ pub struct ItemHeader { min_charisma: u16, base_value: u32, max_stackable: u16, - item_icon: FixedCharSlice<8>, + item_icon: Resref, lore: u16, - ground_icon: FixedCharSlice<8>, + ground_icon: Resref, base_weight: u32, - item_description_generic: i32, - item_description_identified: i32, - description_icon: FixedCharSlice<8>, + item_description_generic: u32, + item_description_identified: u32, + description_icon: Resref, enchantment: u32, - offset_to_extended_headers: i32, - count_of_extended_headers: i16, - offset_to_feature_blocks: i32, - index_to_equipping_feature_blocks: i16, - count_of_feature_blocks: i16, + offset_to_extended_headers: u32, + count_of_extended_headers: u16, + offset_to_feature_blocks: u32, + index_to_equipping_feature_blocks: u16, + count_of_feature_blocks: u16, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/itm_v1.htm#itmv1_Extended_Header -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ItemExtendedHeader { attack_type: u8, // Note zero is very bad here id_required: u8, location: u8, alternative_dice_sides: u8, - use_icon: FixedCharSlice<8>, + use_icon: Resref, target_type: u8, target_count: u8, range: u16, @@ -146,9 +122,10 @@ pub struct ItemExtendedHeader { feature_blocks_index: u16, max_charges: u16, charge_depletion_behaviour: u16, - flags: FixedCharSlice<4>, - projectile_animation: FixedCharSlice<2>, - melee_animation: FixedCharNDArray<2, 3>, + flags: u32, + projectile_animation: u16, + #[br(count = 6)] + melee_animation: Vec, is_arrow: u16, is_bolt: u16, is_bullet: u16, @@ -159,20 +136,14 @@ type ItemFeatureBlock = FeatureBlock; #[cfg(test)] mod tests { + use super::*; + use pretty_assertions::assert_eq; use std::{ fs::File, io::{BufReader, Read}, - mem::size_of, }; - #[test] - fn ensure_size() { - assert_eq!(size_of::(), 114); - assert_eq!(size_of::(), 56); - assert_eq!(size_of::(), 48); - } - #[test] fn valid_item_file_parsed() { let file = File::open("fixtures/gopoof.itm").unwrap(); @@ -182,8 +153,8 @@ mod tests { .read_to_end(&mut buffer) .expect("Could not read to buffer"); let item = Item::new(&buffer); - assert_eq!({ item.header.identified_item_name }, -1); - assert_eq!({ item.header.max_stackable }, 1); + assert_eq!(item.header.identified_item_name, 4294967295); + assert_eq!(item.header.max_stackable, 1); } #[test] diff --git a/models/src/item_table.rs b/models/src/item_table.rs index 4614edf..c77fa85 100644 --- a/models/src/item_table.rs +++ b/models/src/item_table.rs @@ -1,16 +1,16 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::resources::utils::{copy_buff_to_struct, to_u8_slice}; +use crate::common::resref::Resref; +use crate::model::Model; use crate::tlk::Lookup; -use crate::{common::fixed_char_array::FixedCharSlice, model::Model}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_Item -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct ItemReferenceTable { - pub resource_name: FixedCharSlice<8>, + pub resource_name: Resref, // Item expiration time - item creation hour (replace with drained item) pub item_expiration_time_hour: u8, /* @@ -29,71 +29,74 @@ pub struct ItemReferenceTable { impl Model for ItemReferenceTable { fn new(buffer: &[u8]) -> Self { - copy_buff_to_struct::(buffer, 0) + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } fn name(&self, _lookup: &Lookup) -> String { - self.resource_name.to_string() + self.resource_name.0.clone() } fn to_bytes(&self) -> Vec { - to_u8_slice(&self).to_vec() + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_ItemSlots -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct ItemSlots { - helmet: i16, - armor: i16, - shield: i16, - gloves: i16, - left_ring: i16, - right_ring: i16, - amulet: i16, - belt: i16, - boots: i16, - weapon_1: i16, - weapon_2: i16, - weapon_3: i16, - weapon_4: i16, - quiver_1: i16, - quiver_2: i16, - quiver_3: i16, - // Cannot be accesed from gui - quiver_4: i16, - cloak: i16, - quick_item_1: i16, - quick_item_2: i16, - quick_item_3: i16, - inventory_item_1: i16, - inventory_item_2: i16, - inventory_item_3: i16, - inventory_item_4: i16, - inventory_item_5: i16, - inventory_item_6: i16, - inventory_item_7: i16, - inventory_item_8: i16, - inventory_item_9: i16, - inventory_item_10: i16, - inventory_item_11: i16, - inventory_item_12: i16, - inventory_item_13: i16, - inventory_item_14: i16, - inventory_item_15: i16, - inventory_item_16: i16, - magic_weapon: i16, - weapon_slot_selected: i16, - weapon_ability_selected: i16, + helmet: u16, + armor: u16, + shield: u16, + gloves: u16, + left_ring: u16, + right_ring: u16, + amulet: u16, + belt: u16, + boots: u16, + weapon_1: u16, + weapon_2: u16, + weapon_3: u16, + weapon_4: u16, + quiver_1: u16, + quiver_2: u16, + quiver_3: u16, + // Cannot be accessed from gui + quiver_4: u16, + cloak: u16, + quick_item_1: u16, + quick_item_2: u16, + quick_item_3: u16, + inventory_item_1: u16, + inventory_item_2: u16, + inventory_item_3: u16, + inventory_item_4: u16, + inventory_item_5: u16, + inventory_item_6: u16, + inventory_item_7: u16, + inventory_item_8: u16, + inventory_item_9: u16, + inventory_item_10: u16, + inventory_item_11: u16, + inventory_item_12: u16, + inventory_item_13: u16, + inventory_item_14: u16, + inventory_item_15: u16, + inventory_item_16: u16, + magic_weapon: u16, + weapon_slot_selected: u16, + weapon_ability_selected: u16, } impl Model for ItemSlots { fn new(buffer: &[u8]) -> Self { - copy_buff_to_struct::(buffer, 0) + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) @@ -104,16 +107,19 @@ impl Model for ItemSlots { } fn to_bytes(&self) -> Vec { - to_u8_slice(&self).to_vec() + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } #[cfg(test)] mod tests { - use crate::creature::BGEECreature; + use crate::creature::Creature; use super::*; + use pretty_assertions::assert_eq; use std::{ fs::File, io::{BufReader, Read}, @@ -128,51 +134,48 @@ mod tests { .read_to_end(&mut buffer) .expect("Could not read to buffer"); - let header = copy_buff_to_struct::(&buffer, 0); - - let start = usize::try_from(header.offset_to_item_slots).unwrap_or(0); - let item_slots = copy_buff_to_struct::(&buffer, start); + let creature: Creature = Creature::new(&buffer); assert_eq!( - item_slots, + creature.item_slots.unwrap(), ItemSlots { helmet: 2, - armor: -1, - shield: -1, - gloves: -1, + armor: 65535, + shield: 65535, + gloves: 65535, left_ring: 1, right_ring: 3, - amulet: -1, - belt: -1, - boots: -1, - weapon_1: -1, - weapon_2: -1, - weapon_3: -1, - weapon_4: -1, - quiver_1: -1, - quiver_2: -1, - quiver_3: -1, - quiver_4: -1, - cloak: -1, - quick_item_1: -1, - quick_item_2: -1, - quick_item_3: -1, + amulet: 65535, + belt: 65535, + boots: 65535, + weapon_1: 65535, + weapon_2: 65535, + weapon_3: 65535, + weapon_4: 65535, + quiver_1: 65535, + quiver_2: 65535, + quiver_3: 65535, + quiver_4: 65535, + cloak: 65535, + quick_item_1: 65535, + quick_item_2: 65535, + quick_item_3: 65535, inventory_item_1: 4, inventory_item_2: 5, - inventory_item_3: -1, - inventory_item_4: -1, - inventory_item_5: -1, - inventory_item_6: -1, - inventory_item_7: -1, - inventory_item_8: -1, - inventory_item_9: -1, - inventory_item_10: -1, - inventory_item_11: -1, - inventory_item_12: -1, - inventory_item_13: -1, - inventory_item_14: -1, - inventory_item_15: -1, - inventory_item_16: -1, - magic_weapon: -1, + inventory_item_3: 65535, + inventory_item_4: 65535, + inventory_item_5: 65535, + inventory_item_6: 65535, + inventory_item_7: 65535, + inventory_item_8: 65535, + inventory_item_9: 65535, + inventory_item_10: 65535, + inventory_item_11: 65535, + inventory_item_12: 65535, + inventory_item_13: 65535, + inventory_item_14: 65535, + inventory_item_15: 65535, + inventory_item_16: 65535, + magic_weapon: 65535, weapon_slot_selected: 0, weapon_ability_selected: 0 } diff --git a/models/src/key.rs b/models/src/key.rs index 7e462bf..32d6149 100644 --- a/models/src/key.rs +++ b/models/src/key.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, usize}; +use std::fmt::Debug; use crate::common::header::Header; use crate::common::{fixed_char_array::FixedCharSlice, variable_char_array::VariableCharArray}; @@ -19,8 +19,8 @@ impl Key { pub fn new(buffer: &[u8]) -> Self { let header = copy_buff_to_struct::(buffer, 0); - let start = usize::try_from(header.offset_to_bif_entries).unwrap_or(0); - let count = usize::try_from(header.count_of_bif_entries).unwrap_or(0); + let start = header.offset_to_bif_entries as usize; + let count = header.count_of_bif_entries as usize; let bifs = copy_transmute_buff::(buffer, start, count); let bif_entries: Vec = bifs @@ -28,8 +28,8 @@ impl Key { .flat_map(|header| BiffIndex::try_from(header, buffer)) .collect(); - let start = usize::try_from(header.offset_to_resource_entries).unwrap_or(0); - let count = usize::try_from(header.count_of_resource_entries).unwrap_or(0); + let start = header.offset_to_resource_entries as usize; + let count = header.count_of_resource_entries as usize; let raw_resource_entries = copy_transmute_buff::(buffer, start, count); let resource_entries = raw_resource_entries .iter() @@ -49,20 +49,20 @@ impl Key { #[derive(Debug, Copy, Clone)] pub struct KeyHeader { header: Header<4, 4>, - count_of_bif_entries: i32, - count_of_resource_entries: i32, - offset_to_bif_entries: i32, - offset_to_resource_entries: i32, + count_of_bif_entries: u32, + count_of_resource_entries: u32, + offset_to_bif_entries: u32, + offset_to_resource_entries: u32, } //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/key_v1.htm#keyv1_BifIndices #[repr(C, packed)] #[derive(Debug, Copy, Clone)] pub struct BiffIndexHeader { - file_length: i32, - offset_to_file_name: i32, - file_name_length: i16, - file_location: i16, + file_length: u32, + offset_to_file_name: u32, + file_name_length: u16, + file_location: u16, } #[derive(Debug)] @@ -73,8 +73,8 @@ pub struct BiffIndex { impl BiffIndex { fn try_from(header: &BiffIndexHeader, buffer: &[u8]) -> Option { - let start = usize::try_from(header.offset_to_file_name).unwrap_or(0); - let end = start + usize::try_from(header.file_name_length).unwrap_or(0); + let start = header.offset_to_file_name as usize; + let end = start + header.file_name_length as usize; buffer.get(start..end).map(|buff| BiffIndex { header: *header, name: VariableCharArray(buff.into()), diff --git a/models/src/save.rs b/models/src/save.rs index d862433..583a968 100644 --- a/models/src/save.rs +++ b/models/src/save.rs @@ -1,11 +1,10 @@ use flate2::bufread::ZlibDecoder; use serde::{Deserialize, Serialize}; -use std::{io::Read, rc::Rc}; +use std::io::Read; use crate::{ common::{header::Header, variable_char_array::VariableCharArray}, from_buffer, - model::Model, resources::{types::extension_to_resource_type, utils::copy_buff_to_struct}, }; @@ -14,15 +13,14 @@ use crate::{ pub struct Save { pub header: Header<4, 4>, pub files: Vec, - #[serde(skip)] - pub uncompressed_files: Vec>, + //#[serde(skip)] + //pub uncompressed_files: Vec>, } impl Save { pub fn new(buffer: &[u8]) -> Self { let header = copy_buff_to_struct::>(buffer, 0); let mut files = vec![]; - let uncompressed_files = vec![]; let mut counter = 8; while counter <= (buffer.len() - 1) { let file = File::new(buffer.get(counter..).unwrap_or_default()); @@ -33,11 +31,7 @@ impl Save { files.push(file); } - Save { - header, - files, - uncompressed_files, - } + Save { header, files } } pub fn decompress(&mut self) { let mut uncompressed_files = Vec::with_capacity(self.files.len()); @@ -50,7 +44,6 @@ impl Save { uncompressed_files.push(model); } } - self.uncompressed_files = uncompressed_files; } } @@ -73,7 +66,7 @@ impl File { .try_into() .unwrap_or_default(), ); - let end: usize = usize::try_from(length_of_filename).unwrap_or(0); + let end = length_of_filename as usize; let filename = VariableCharArray(buffer.get(4..(end + 4)).unwrap_or_default().into()); let uncompressed_data_length = u32::from_ne_bytes( buffer @@ -123,7 +116,7 @@ mod tests { io::{BufReader, Read}, }; - use pretty_assertions::{assert_eq, assert_ne}; + use pretty_assertions::assert_eq; #[test] fn uncompress_files() { @@ -135,7 +128,6 @@ mod tests { .expect("Could not read to buffer"); let mut save = Save::new(&buffer); save.decompress(); - assert_ne!(save.uncompressed_files.len(), 0); } #[test] @@ -436,7 +428,7 @@ mod tests { file.compressed_data.len(), file.compressed_data_length as usize ); - assert_eq!(file.filename.to_string(), file_names[i]); + assert_eq!(file.filename.to_string().replace("\0", ""), file_names[i]); } } } diff --git a/models/src/spell.rs b/models/src/spell.rs index 44c16b6..3bea82b 100644 --- a/models/src/spell.rs +++ b/models/src/spell.rs @@ -1,88 +1,73 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; use crate::common::feature_block::FeatureBlock; -use crate::common::fixed_char_array::FixedCharSlice; -use crate::common::header::Header; -use crate::common::signed_fixed_char_array::SignedFixedCharSlice; +use crate::common::resref::Resref; +use crate::common::strref::Strref; use crate::model::Model; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; + use crate::tlk::Lookup; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/spl_v1.htm -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Spell { + #[serde(flatten)] pub header: SpellHeader, + #[serde(flatten)] + #[br(count=header.count_of_extended_headers)] pub extended_headers: Vec, + #[serde(flatten)] + #[br(parse_with=binrw::helpers::until_eof)] pub equipping_feature_blocks: Vec, } impl Model for Spell { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_extended_headers).unwrap_or(0); - let count = usize::try_from(header.count_of_extended_headers).unwrap_or(0); - let extended_headers = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_feature_block_table).unwrap_or(0); - let count = (buffer.len() - start) / std::mem::size_of::(); - let equipping_feature_blocks = - copy_transmute_buff::(buffer, start, count); - - Self { - header, - extended_headers, - equipping_feature_blocks, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } + fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } - fn name(&self, lookup: &Lookup) -> String { - let name = if self.header.identified_spell_name > -1 { - lookup - .strings - .get(self.header.identified_spell_name as usize) - .unwrap() - .to_string() - } else if self.header.unidentified_spell_name > -1 { - lookup - .strings - .get(self.header.unidentified_spell_name as usize) - .unwrap() - .to_string() - } else { - format!("{}", { self.header.identified_spell_name }) - }; - format!("{}.spl", name) + fn name(&self, _lookup: &Lookup) -> String { + todo!() } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.extended_headers)); - out.extend(vec_to_u8_slice(&self.equipping_feature_blocks)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/spl_v1.htm#splv1_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct SpellHeader { - header: Header<4, 4>, - unidentified_spell_name: i32, - identified_spell_name: i32, - completion_sound: FixedCharSlice<8>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + version: String, + unidentified_spell_name: u32, + identified_spell_name: u32, + completion_sound: Resref, // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/spl_v2.htm#Header_Flags flags: u32, spell_type: u16, exclusion_flags: u32, - casting_graphics: FixedCharSlice<2>, + casting_graphics: u16, min_level: u8, primary_spell_school: u8, min_strength: u8, @@ -99,29 +84,28 @@ pub struct SpellHeader { min_charisma: u16, spell_level: u32, max_stackable: u16, - spell_book_icon: FixedCharSlice<8>, + spell_book_icon: Resref, lore: u16, - ground_icon: FixedCharSlice<8>, + ground_icon: Resref, base_weight: u32, - spell_description_generic: SignedFixedCharSlice<4>, - spell_description_identified: SignedFixedCharSlice<4>, - description_icon: SignedFixedCharSlice<8>, + spell_description_generic: Strref, + spell_description_identified: Strref, + description_icon: Resref, enchantment: u32, - offset_to_extended_headers: i32, - count_of_extended_headers: i16, - offset_to_feature_block_table: i32, - offset_to_casting_feature_blocks: i16, - count_of_casting_feature_blocks: i16, + offset_to_extended_headers: u32, + count_of_extended_headers: u16, + offset_to_feature_block_table: u32, + offset_to_casting_feature_blocks: u16, + count_of_casting_feature_blocks: u16, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/spl_v1.htm#splv1_Extended_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct SpellExtendedHeader { spell_form: u8, friendly: u8, location: u16, - memorised_icon: FixedCharSlice<8>, + memorised_icon: Resref, target_type: u8, target_count: u8, range: u16, @@ -132,8 +116,8 @@ pub struct SpellExtendedHeader { dice_thrown: u16, enchanted: u16, damage_type: u16, - count_of_feature_blocks: i16, - offset_to_feature_blocks: i16, + count_of_feature_blocks: u16, + offset_to_feature_blocks: u16, charges: u16, charge_depletion_behaviour: u16, projectile: u16, @@ -145,21 +129,15 @@ type SpellFeatureBlock = FeatureBlock; #[cfg(test)] mod tests { - use crate::spell::Spell; + use crate::common::resref::Resref; use super::*; + use pretty_assertions::assert_eq; use std::{ fs::File, io::{BufReader, Read}, }; - #[test] - fn valid_sizes() { - assert_eq!(std::mem::size_of::(), 114); - assert_eq!(std::mem::size_of::(), 40); - assert_eq!(std::mem::size_of::(), 48); - } - #[test] fn valid_creature_file_item_table_parsed() { let file = File::open("fixtures/gate1.spl").unwrap(); @@ -173,17 +151,15 @@ mod tests { assert_eq!( spell.header, SpellHeader { - header: Header { - signature: "SPL ".into(), - version: "V1 ".into(), - }, + signature: "SPL ".to_string(), + version: "V1 ".to_string(), unidentified_spell_name: 14260, identified_spell_name: 9999999, - completion_sound: FixedCharSlice([67, 65, 83, 95, 77, 48, 51, 0]), + completion_sound: Resref("CAS_M03\0".to_string()), flags: 0, spell_type: 1, exclusion_flags: 0, - casting_graphics: FixedCharSlice([18, 0]), + casting_graphics: 18, min_level: 0, primary_spell_school: 2, min_strength: 0, @@ -200,13 +176,13 @@ mod tests { min_charisma: 0, spell_level: 9, max_stackable: 1, - spell_book_icon: FixedCharSlice([83, 80, 87, 73, 57, 48, 53, 67]), + spell_book_icon: Resref("SPWI905C".to_string()), lore: 0, - ground_icon: FixedCharSlice([0, 0, 114, 98, 0, 0, 85, 110]), + ground_icon: Resref("\0\0rb\0\0Un".to_string()), base_weight: 0, - spell_description_generic: SignedFixedCharSlice([-1, -1, -1, -1]), - spell_description_identified: SignedFixedCharSlice([127, -106, -104, 0]), - description_icon: SignedFixedCharSlice([0, 0, 0, 104, -122, 64, 0, 5]), + spell_description_generic: Strref(4294967295), + spell_description_identified: Strref(9999999), + description_icon: Resref("".to_string()), enchantment: 0, offset_to_extended_headers: 114, count_of_extended_headers: 1, @@ -228,10 +204,10 @@ mod tests { duration: 100000, probability_1: 39, probability_2: 0, - resource: "balorsu".into(), + resource: Resref("balorsu\0".to_string()), dice_thrown_max_level: 0, dice_sides_min_level: 0, - saving_throw_type: "".into(), + saving_throw_type: vec![0, 0, 0, 0], saving_throw_bonus: 0, stacking_id: 0 }] diff --git a/models/src/spell_table.rs b/models/src/spell_table.rs index 2fbb86e..8efe66d 100644 --- a/models/src/spell_table.rs +++ b/models/src/spell_table.rs @@ -1,30 +1,31 @@ use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_array::FixedCharSlice; +use crate::common::resref::Resref; +use binrw::{BinRead, BinWrite}; -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +// https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_KnownSpell +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct KnownSpells { - pub spell_name: FixedCharSlice<8>, + pub spell_name: Resref, pub spell_level: u16, pub spell_type: u16, } -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +// https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_MemSpellInfo +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct SpellMemorizationInfo { pub spell_level: u16, pub number_of_spells_memorizable: u16, pub number_of_spells_memorizable_after_effects: u16, pub spell_type: u16, - pub index_to_spell_table: i32, - pub count_of_memorizable_spell_tables: i32, + pub index_to_spell_table: u32, + pub count_of_memorizable_spell_tables: u32, } -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +// https://gibberlings3.github.io/iesdp/file_formats/ie_formats/cre_v1.htm#CREV1_0_MemSpell +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct SpellMemorizationTable { - pub spell_name: FixedCharSlice<8>, + pub spell_name: Resref, pub memorised: u32, } @@ -65,139 +66,139 @@ mod tests { creature.memorized_spells, vec![ SpellMemorizationTable { - spell_name: "SPPR103".into(), + spell_name: Resref("SPPR103\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR103".into(), + spell_name: Resref("SPPR103\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR109".into(), + spell_name: Resref("SPPR109\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR101".into(), + spell_name: Resref("SPPR101\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR203".into(), + spell_name: Resref("SPPR203\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR208".into(), + spell_name: Resref("SPPR208\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR211".into(), + spell_name: Resref("SPPR211\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR212".into(), + spell_name: Resref("SPPR212\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR312".into(), + spell_name: Resref("SPPR312\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR313".into(), + spell_name: Resref("SPPR313\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR315".into(), + spell_name: Resref("SPPR315\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR401".into(), + spell_name: Resref("SPPR401\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR413".into(), + spell_name: Resref("SPPR413\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR411".into(), + spell_name: Resref("SPPR411\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR502".into(), + spell_name: Resref("SPPR502\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPPR503".into(), + spell_name: Resref("SPPR503\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI113".into(), + spell_name: Resref("SPWI113\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI112".into(), + spell_name: Resref("SPWI112\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI110".into(), + spell_name: Resref("SPWI110\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI105".into(), + spell_name: Resref("SPWI105\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI213".into(), + spell_name: Resref("SPWI213\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI220".into(), + spell_name: Resref("SPWI220\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI211".into(), + spell_name: Resref("SPWI211\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI203".into(), + spell_name: Resref("SPWI203\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI312".into(), + spell_name: Resref("SPWI312\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI311".into(), + spell_name: Resref("SPWI311\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI318".into(), + spell_name: Resref("SPWI318\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI308".into(), + spell_name: Resref("SPWI308\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI408".into(), + spell_name: Resref("SPWI408\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI405".into(), + spell_name: Resref("SPWI405\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI406".into(), + spell_name: Resref("SPWI406\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI510".into(), + spell_name: Resref("SPWI510\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI505".into(), + spell_name: Resref("SPWI505\0".to_string()), memorised: 1, }, SpellMemorizationTable { - spell_name: "SPWI522".into(), + spell_name: Resref("SPWI522\0".to_string()), memorised: 1, }, ] diff --git a/models/src/store.rs b/models/src/store.rs index 4786d7b..44fee5f 100644 --- a/models/src/store.rs +++ b/models/src/store.rs @@ -1,142 +1,129 @@ use std::rc::Rc; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; +use crate::common::resref::Resref; +use crate::common::strref::Strref; +use crate::model::Model; use crate::tlk::Lookup; -use crate::{ - common::{fixed_char_array::FixedCharSlice, header::Header}, - model::Model, -}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Store { + #[serde(flatten)] pub header: StoreHeader, + #[serde(flatten)] + #[br(count=header.count_of_items_for_sale_section)] pub items_for_sale: Vec, + #[serde(flatten)] + #[br(count=header.count_of_drinks_section)] pub drinks_for_sale: Vec, + #[serde(flatten)] + #[br(count=header.count_of_cures_section)] pub cures_for_sale: Vec, - pub items_purchased_here: Vec, + #[serde(flatten)] + #[br(count=header.count_of_items_in_items_purchased_section)] + pub items_purchased_here: Vec, } impl Model for Store { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let start = usize::try_from(header.offset_to_items_for_sale_section).unwrap_or(0); - let count = usize::try_from(header.count_of_items_for_sale_section).unwrap_or(0); - let items_for_sale = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_drinks_section).unwrap_or(0); - let count = usize::try_from(header.count_of_drinks_section).unwrap_or(0); - let drinks_for_sale = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_cures_section).unwrap_or(0); - let count = usize::try_from(header.count_of_cures_section).unwrap_or(0); - let cures_for_sale = copy_transmute_buff::(buffer, start, count); - - let start = usize::try_from(header.offset_to_items_purchased_section).unwrap_or(0); - let count = usize::try_from(header.count_of_items_in_items_purchased_section).unwrap_or(0); - let items_purchased_here = copy_transmute_buff::(buffer, start, count); - Self { - header, - items_for_sale, - drinks_for_sale, - cures_for_sale, - items_purchased_here, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } + fn create_as_rc(buffer: &[u8]) -> Rc { Rc::new(Self::new(buffer)) } fn name(&self, _lookup: &Lookup) -> String { - self.header.name.to_string() + todo!() } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.items_for_sale)); - out.extend(vec_to_u8_slice(&self.drinks_for_sale)); - out.extend(vec_to_u8_slice(&self.cures_for_sale)); - out.extend(vec_to_u8_slice(&self.items_purchased_here)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm#storv1_0_Header -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct StoreHeader { - pub header: Header<4, 4>, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, // (0=Store, 1=Tavern, 2=Inn, 3=Temple, 5=Container) - pub store_type: i32, - pub name: FixedCharSlice<4>, - pub flags: i32, - pub sell_price_markup: i32, - pub buy_price_markup: i32, - pub depreciation_rate: i32, - pub chance_of_steal_failure: i16, - pub capacity: i16, + pub store_type: u32, + pub name: Strref, + pub flags: u32, + pub sell_price_markup: u32, + pub buy_price_markup: u32, + pub depreciation_rate: u32, + pub chance_of_steal_failure: u16, + pub capacity: u16, #[serde(skip)] - _unknown1: [i8; 8], - pub offset_to_items_purchased_section: i32, - pub count_of_items_in_items_purchased_section: i32, - pub offset_to_items_for_sale_section: i32, - pub count_of_items_for_sale_section: i32, - pub lore: i32, - pub id_price: i32, - pub rumours_tavern: [i8; 8], - pub offset_to_drinks_section: i32, - pub count_of_drinks_section: i32, - pub rumours_temple: [i8; 8], - pub room_flags: i32, - pub price_of_a_peasant_room: i32, - pub price_of_a_merchant_room: i32, - pub price_of_a_noble_room: i32, - pub price_of_a_royal_room: i32, - pub offset_to_cures_section: i32, - pub count_of_cures_section: i32, + #[br(count = 8)] + _unknown1: Vec, + pub offset_to_items_purchased_section: u32, + pub count_of_items_in_items_purchased_section: u32, + pub offset_to_items_for_sale_section: u32, + pub count_of_items_for_sale_section: u32, + pub lore: u32, + pub id_price: u32, + pub rumours_tavern: Resref, + pub offset_to_drinks_section: u32, + pub count_of_drinks_section: u32, + pub rumours_temple: Resref, + pub room_flags: u32, + pub price_of_a_peasant_room: u32, + pub price_of_a_merchant_room: u32, + pub price_of_a_noble_room: u32, + pub price_of_a_royal_room: u32, + pub offset_to_cures_section: u32, + pub count_of_cures_section: u32, #[serde(skip)] - _unknown2: [i8; 32], + #[br(count = 36)] + _unknown2: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm#storv1_0_Sale -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct ItemsForSale { - pub filename_of_item: [i8; 8], - pub item_expiration_time: i16, - pub quantity_charges_1: i16, - pub quantity_charges_2: i16, - pub quantity_charges_3: i16, - pub flags: i32, - pub amount_of_this_item_in_stock: i32, + pub filename_of_item: Resref, + pub item_expiration_time: u16, + pub quantity_charges_1: u16, + pub quantity_charges_2: u16, + pub quantity_charges_3: u16, + pub flags: u32, + pub amount_of_this_item_in_stock: u32, // (0=limited stock, 1=infinite stock) - pub infinite_supply_flag: i32, + pub infinite_supply_flag: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm#storv1_0_Drink -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct DrinksForSale { - pub rumour_resource: [i8; 8], - pub drink_name: FixedCharSlice<4>, - pub drink_price: i32, - pub alcoholic_strength: i32, + pub rumour_resource: Resref, + pub drink_name: Strref, + pub drink_price: u32, + pub alcoholic_strength: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm#storv1_0_Cure -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct CuresForSale { - pub filename_of_spell: [i8; 8], - pub spell_price: i32, + pub filename_of_spell: Resref, + pub spell_price: u32, } - -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub struct ItemsPurchasedHere(i32); diff --git a/models/src/tlk.rs b/models/src/tlk.rs index 7a76068..8232cc7 100644 --- a/models/src/tlk.rs +++ b/models/src/tlk.rs @@ -1,51 +1,28 @@ -use std::rc::Rc; +use std::{io::SeekFrom, rc::Rc}; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::{ - common::{ - header::Header, signed_fixed_char_array::SignedFixedCharSlice, - variable_char_array::VariableCharArray, - }, - model::Model, - resources::utils::{copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice}, -}; - -// This is hard coded by the file format -const START: usize = 18; +use crate::{common::resref::Resref, model::Model}; // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/tlk_v1.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct Lookup { + #[serde(flatten)] pub header: TLKHeader, + #[serde(flatten)] + #[br(count=header.count_of_entries, args{ inner: (&header,) })] pub entries: Vec, - pub strings: Vec, } impl Model for Lookup { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let count = usize::try_from(header.count_of_entries).unwrap_or(0); - let entries = copy_transmute_buff::(buffer, START, count); - - let start = usize::try_from(header.offset_to_strings).unwrap_or(0); - let strings = entries - .iter() - .map(|entry| { - let buff_start = start - + usize::try_from(entry.offset_of_this_string_relative_to_the_strings_section) - .unwrap_or(0); - let buff_end = - buff_start + usize::try_from(entry.length_of_this_string).unwrap_or(0); - VariableCharArray(buffer.get(buff_start..buff_end).unwrap().into()) - }) - .collect(); - Self { - header, - entries, - strings, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } @@ -58,26 +35,31 @@ impl Model for Lookup { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.entries)); - out.extend(vec_to_u8_slice(&self.strings)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/tlk_v1.htm#tlkv1_Header -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] pub struct TLKHeader { - pub header: Header<4, 4>, - pub language_id: i16, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, + pub language_id: u16, pub count_of_entries: u32, pub offset_to_strings: u32, } //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/tlk_v1.htm#tlkv1_Entry -#[repr(C, packed)] -#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, PartialEq, BinRead, BinWrite, Serialize, Deserialize)] +#[br(import(header: &TLKHeader))] pub struct TLKEntry { /* 00 - No message data @@ -86,14 +68,19 @@ pub struct TLKEntry { 03 - Standard message. Ambient message. Used for sound without text (BG1) or message displayed over characters head (BG2) , Message with tags (for instance ) for all games except BG2 04 - Token exists (for instance ), BG2 and EEs only */ - pub bit_field: i16, - pub resource_name_of_associated_sound: SignedFixedCharSlice<8>, + pub bit_field: u16, + pub resource_name_of_associated_sound: Resref, // Unused, at minimum in BG1 pub volume_variance: u32, // Unused, at minimum in BG1 pub pitch_variance: u32, - pub offset_of_this_string_relative_to_the_strings_section: u32, + // Offset of this string relative to the strings section + pub offset_to_this_string: u32, pub length_of_this_string: u32, + #[br(count=length_of_this_string, seek_before=SeekFrom::Start(offset_to_this_string as u64 + header.offset_to_strings as u64), restore_position)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub tlk_string: String, } #[cfg(test)] @@ -117,10 +104,8 @@ mod tests { assert_eq!( lookup.header, TLKHeader { - header: Header { - signature: "TLK ".into(), - version: "V1 ".into(), - }, + signature: "TLK ".to_string(), + version: "V1 ".to_string(), language_id: 0, count_of_entries: 34000, offset_to_strings: 884018, @@ -131,14 +116,27 @@ mod tests { entry, &TLKEntry { bit_field: 1, - resource_name_of_associated_sound: "".into(), + resource_name_of_associated_sound: Resref("\0\0\0\0\0\0\0\0".to_string()), + volume_variance: 0, + pitch_variance: 0, + offset_to_this_string: 49264, + length_of_this_string: 213, + tlk_string: " 'Twas some three hundred years hence, but folk still cringe at the mention of the destruction at Ulcaster School. I've not met a soul who claims to know why it occurred, and none that were there are alive to say.".to_string() + } + ); + + let entry = lookup.entries.last().expect("Failed to find entry"); + assert_eq!( + entry, + &TLKEntry { + bit_field: 1, + resource_name_of_associated_sound: Resref("\0\0\0\0\0\0\0\0".to_string()), volume_variance: 0, pitch_variance: 0, - offset_of_this_string_relative_to_the_strings_section: 49264, - length_of_this_string: 213 + offset_to_this_string: 3855179, + length_of_this_string: 11, + tlk_string: "placeholder".to_string() } ); - let content = lookup.strings.get(400).expect("Failed to find entry"); - assert_eq!(content.to_string()," 'Twas some three hundred years hence, but folk still cringe at the mention of the destruction at Ulcaster School. I've not met a soul who claims to know why it occurred, and none that were there are alive to say.") } } diff --git a/models/src/twoda.rs b/models/src/twoda.rs index efcefd7..384e4dd 100644 --- a/models/src/twoda.rs +++ b/models/src/twoda.rs @@ -1,65 +1,25 @@ -use std::{rc::Rc, vec}; +use std::rc::Rc; use std::fmt::Debug; +use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::fixed_char_array::FixedCharSlice; -use crate::common::header::Header; -use crate::common::variable_char_array::VariableCharArray; use crate::model::Model; -use crate::resources::utils::{row_parser, to_u8_slice, vec_to_u8_slice}; use crate::tlk::Lookup; //https://gibberlings3.github.io/iesdp/file_formats/ie_formats/2da.htm -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct TwoDA { - pub header: Header<3, 4>, - pub default_value: VariableCharArray, - pub data_entries: DataEntry, + #[br(parse_with = binrw::helpers::until_eof, map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub data: String, } impl Model for TwoDA { fn new(buffer: &[u8]) -> Self { - // Parse Headers - let (headers, end) = row_parser(buffer, 0); - - let signature = if headers.first().is_some() { - FixedCharSlice::<3>::from(&buffer[0..3]) - } else { - FixedCharSlice::<3>::default() - }; - let version = match headers.last() { - Some(x) => FixedCharSlice::<4>::from(x.0.as_ref()), - _ => FixedCharSlice::<4>::from(signature.0.as_ref()), - }; - let header = Header { signature, version }; - - // Parse Default Value - let (default_values, end) = row_parser(buffer, end); - - // Parse Data Entry Headers - let (data_entry_headers, mut end) = row_parser(buffer, end); - - // Parse Values - let mut values = vec![]; - while end < buffer.len() { - let (row, row_end) = row_parser(buffer, end); - values.push(row); - if end == row_end { - break; - } - end = row_end; - } - Self { - header, - default_value: default_values.first().unwrap().clone(), - data_entries: DataEntry { - data_entry_headers, - values, - }, - } + let mut reader = Cursor::new(buffer); + reader.read_le().unwrap() } fn create_as_rc(buffer: &[u8]) -> Rc { @@ -71,59 +31,8 @@ impl Model for TwoDA { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(to_u8_slice(&self.default_value)); - out.extend(vec_to_u8_slice(&self.data_entries.data_entry_headers)); - for row in &self.data_entries.values { - out.extend(vec_to_u8_slice(row)); - } - out - } -} - -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] -pub struct DataEntry { - pub data_entry_headers: Vec, - pub values: Vec>, -} - -#[cfg(test)] -mod tests { - - use super::*; - use std::{ - fs::File, - io::{BufReader, Read}, - }; - - #[test] - fn valid_item_file_parsed() { - let file = File::open("fixtures/xpcap.2da").unwrap(); - let mut reader = BufReader::new(file); - let mut buffer = Vec::new(); - reader - .read_to_end(&mut buffer) - .expect("Could not read to buffer"); - let item = TwoDA::new(&buffer); - assert_eq!( - item.header, - Header { - version: "V1.0".into(), - signature: "2DA".into(), - } - ); - assert_eq!(item.default_value.to_string(), "2950000".to_string()); - assert_eq!( - item.data_entries - .data_entry_headers - .first() - .unwrap() - .to_string(), - "VALUE" - ); - let last_values = item.data_entries.values.last().unwrap(); - assert_eq!(last_values.first().unwrap().to_string(), "SHAMAN"); - assert_eq!(last_values.last().unwrap().to_string(), "-1"); + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } diff --git a/models/src/world_map.rs b/models/src/world_map.rs index 413801c..5794b65 100644 --- a/models/src/world_map.rs +++ b/models/src/world_map.rs @@ -1,51 +1,30 @@ use std::rc::Rc; +use binrw::{io::Cursor, io::SeekFrom, BinRead, BinReaderExt, BinWrite}; use serde::{Deserialize, Serialize}; -use crate::common::header::Header; -use crate::resources::utils::{ - copy_buff_to_struct, copy_transmute_buff, to_u8_slice, vec_to_u8_slice, -}; +use crate::common::resref::Resref; +use crate::common::strref::Strref; +use crate::model::Model; use crate::tlk::Lookup; -use crate::{common::fixed_char_array::FixedCharSlice, model::Model}; -#[repr(C)] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct WorldMap { #[serde(flatten)] pub header: WorldMapHeader, #[serde(flatten)] + #[br(count=header.count_of_worldmap_entries, seek_before=SeekFrom::Start(header.offset_to_worldmap_entries as u64))] pub world_map_entries: Vec, - pub area_entries: Vec, - pub area_link_entries: Vec, } impl Model for WorldMap { fn new(buffer: &[u8]) -> Self { - let header = copy_buff_to_struct::(buffer, 0); - - let count = usize::try_from(header.count_of_worldmap_entries).unwrap_or(0); - let start = usize::try_from(header.offset_to_worldmap_entries).unwrap_or(0); - let world_map_entries: Vec = - copy_transmute_buff::(buffer, start, count); - - let mut area_entries = vec![]; - let mut area_link_entries = vec![]; - world_map_entries.iter().for_each(|world_map_entry| { - let start = usize::try_from(world_map_entry.offset_to_area_entries).unwrap_or(0); - let count = usize::try_from(world_map_entry.count_of_area_entries).unwrap_or(0); - area_entries.extend(copy_transmute_buff::(buffer, start, count)); - - let start = usize::try_from(world_map_entry.offset_to_area_link_entries).unwrap_or(0); - let count = usize::try_from(world_map_entry.count_of_area_link_entries).unwrap_or(0); - area_link_entries.extend(copy_transmute_buff::(buffer, start, count)); - }); - - Self { - header, - world_map_entries, - area_entries, - area_link_entries, + let mut reader = Cursor::new(buffer); + match reader.read_le() { + Ok(res) => res, + Err(err) => { + panic!("Errored with {:?}, dumping buffer: {:?}", err, buffer); + } } } @@ -58,59 +37,72 @@ impl Model for WorldMap { } fn to_bytes(&self) -> Vec { - let mut out = to_u8_slice(&self.header).to_vec(); - out.extend(vec_to_u8_slice(&self.world_map_entries)); - out.extend(vec_to_u8_slice(&self.area_entries)); - out.extend(vec_to_u8_slice(&self.area_link_entries)); - out + let mut writer = Cursor::new(Vec::new()); + self.write_le(&mut writer).unwrap(); + writer.into_inner() } } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/wmap_v1.htm#wmapv1_0_Header -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct WorldMapHeader { - pub header: Header<4, 4>, - pub count_of_worldmap_entries: i32, - pub offset_to_worldmap_entries: i32, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub signature: String, + #[br(count = 4)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub version: String, + pub count_of_worldmap_entries: u32, + pub offset_to_worldmap_entries: u32, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/wmap_v1.htm#wmapv1_0_Entry -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct WorldMapEntry { - pub background_image_mos_file: FixedCharSlice<8>, + pub background_image_mos_file: Resref, pub width: u32, pub height: u32, pub map_number: u32, - pub area_name: FixedCharSlice<4>, + pub area_name: Strref, pub start_centered_on_x: u32, pub start_centered_on_y: u32, - pub count_of_area_entries: i32, - pub offset_to_area_entries: i32, - pub offset_to_area_link_entries: i32, - pub count_of_area_link_entries: i32, - pub map_icons_bam_file: FixedCharSlice<8>, - // BGEE feild only + pub count_of_area_entries: u32, + pub offset_to_area_entries: u32, + pub offset_to_area_link_entries: u32, + pub count_of_area_link_entries: u32, + pub map_icons_bam_file: Resref, + // BGEE field only pub flags: u32, #[serde(skip)] - _unused: FixedCharSlice<128>, + #[br(count = 128)] + _unused: Vec, + #[serde(flatten)] + #[br(count=count_of_area_entries, seek_before=SeekFrom::Start(offset_to_area_entries as u64))] + pub area_entries: Vec, + #[serde(flatten)] + #[br(count=count_of_area_link_entries, seek_before=SeekFrom::Start(offset_to_area_link_entries as u64))] + pub area_link_entries: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/wmap_v1.htm#wmapv1_0_Area -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct AreaEntry { - pub area_resref: FixedCharSlice<8>, - pub area_name_short: FixedCharSlice<8>, - pub area_name_long: FixedCharSlice<8>, - pub bitmask_indicating_status_of_area: FixedCharSlice<4>, + pub area_resref: Resref, + pub area_name_short: Resref, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub area_name_long: String, + #[br(count = 4)] + pub bitmask_indicating_status_of_area: Vec, pub bam_file_sequence_icons: u32, pub x_coordinate: u32, pub y_coordinate: u32, - pub name_caption: FixedCharSlice<4>, - pub name_tooltips: FixedCharSlice<4>, - pub loading_screen_mos_file: FixedCharSlice<8>, + pub name_caption: Strref, + pub name_tooltips: Strref, + pub loading_screen_mos_file: Resref, pub link_index_north: u32, pub link_count_north: u32, pub link_index_west: u32, @@ -120,25 +112,29 @@ pub struct AreaEntry { pub link_index_east: u32, pub link_count_east: u32, #[serde(skip)] - _unused: FixedCharSlice<128>, + #[br(count = 128)] + _unused: Vec, } // https://gibberlings3.github.io/iesdp/file_formats/ie_formats/wmap_v1.htm#wmapv1_0_AreaLink -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, BinRead, BinWrite, Serialize, Deserialize)] pub struct AreaLink { pub index_of_destination_area: u32, - pub entry_point: FixedCharSlice<32>, + #[br(count = 32)] + #[br(map = |s: Vec| String::from_utf8(s).unwrap_or_default())] + #[bw(map = |x| x.parse::().unwrap())] + pub entry_point: String, pub travel_time: u32, pub default_entry_location: u32, - pub random_encounter_area_1: FixedCharSlice<8>, - pub random_encounter_area_2: FixedCharSlice<8>, - pub random_encounter_area_3: FixedCharSlice<8>, - pub random_encounter_area_4: FixedCharSlice<8>, - pub random_encounter_area_5: FixedCharSlice<8>, + pub random_encounter_area_1: Resref, + pub random_encounter_area_2: Resref, + pub random_encounter_area_3: Resref, + pub random_encounter_area_4: Resref, + pub random_encounter_area_5: Resref, pub random_encounter_probability: u32, #[serde(skip)] - _unused: FixedCharSlice<128>, + #[br(count = 128)] + _unused: Vec, } #[cfg(test)] @@ -161,7 +157,21 @@ mod tests { .read_to_end(&mut buffer) .expect("Could not read to buffer"); let world = WorldMap::new(&buffer); - assert_eq!(world.area_entries.len(), 58); - assert_eq!(world.area_link_entries.len(), 208) + let mut count_of_area_entries = 0; + let mut count_of_area_link = 0; + for entry in world.world_map_entries { + count_of_area_entries += entry.count_of_area_entries; + count_of_area_link += entry.count_of_area_link_entries; + assert_eq!( + entry.count_of_area_entries as usize, + entry.area_entries.len() + ); + assert_eq!( + entry.count_of_area_link_entries as usize, + entry.area_link_entries.len() + ); + } + assert_eq!(count_of_area_entries, 58); + assert_eq!(count_of_area_link, 208) } } diff --git a/cli/src/args.rs b/src/args.rs similarity index 100% rename from cli/src/args.rs rename to src/args.rs diff --git a/cli/src/lib.rs b/src/cli.rs similarity index 97% rename from cli/src/lib.rs rename to src/cli.rs index 46fc984..0b3aebf 100644 --- a/cli/src/lib.rs +++ b/src/cli.rs @@ -1,13 +1,12 @@ -pub mod args; use std::{ fs::{self, File}, - io::{self, BufReader, Read, Write}, + io::{self, Read, Write}, path::Path, process::exit, str, }; -use args::Args; +use binrw::io::BufReader; use models::{ biff::Biff, from_buffer, from_json, @@ -20,6 +19,8 @@ use models::{ use erased_serde::Serializer; +use crate::args::Args; + fn write_file(path: &Path, extension: &str, buffer: &[u8]) { let file_name = Path::new(path.file_stem().unwrap_or_default()).with_extension(extension); if let Ok(mut file) = File::create(file_name) { @@ -35,7 +36,7 @@ fn json_back_to_ie_type(path: &Path) { .unwrap_or_default() .to_str() .unwrap_or_default() - .split(".") + .split('.') .nth(1) .unwrap_or_default() .to_ascii_lowercase(); diff --git a/src/main.rs b/src/main.rs index 30cbc9f..97a3812 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ use clap::Parser; -use cli::args::Args; + +use args::Args; + +pub mod args; +pub mod cli; fn main() { let args: Args = Args::parse();