Compare commits

...
Sign in to create a new pull request.

118 commits

Author SHA1 Message Date
48683638f6
Merge pull request 'chore(deps): update rust crate bincode to v2' (#204) from renovate/bincode-2.x into master
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #204
2025-04-22 14:51:45 +02:00
f5940f995e
Merge pull request 'Fix build script' (#240) from feat/ci into master
Reviewed-on: #240
2025-04-22 14:51:24 +02:00
5bb0f2acf5
Fix build script 2025-04-22 14:50:37 +02:00
60d9ec0580
Merge pull request 'chore(deps): update rust crate cli-table to 0.5.0' (#210) from renovate/cli-table-0.x into master
Some checks failed
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #210
2025-04-22 00:16:24 +02:00
cd4a953a63
Update bincode
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-22 00:14:43 +02:00
72ab8811c3
Ignore broken pipe error when printing dictionary
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
stdout closing prematurely is normal behaviour, e.g. piping into `head`.
2025-04-22 00:06:34 +02:00
3c7393f28a
Merge pull request 'chore(deps): update rust crate steamlocate to v2.0.1' (#217) from renovate/steamlocate-2.x-lockfile into master
Some checks failed
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #217
2025-04-22 00:01:30 +02:00
afb5bc0795
chore(deps): update rust crate bincode to v2
Some checks failed
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 22:01:13 +00:00
ddc69112bc
chore(deps): update rust crate cli-table to 0.5.0
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 22:01:08 +00:00
cf8daeb3f2
Merge pull request 'fix(deps): update rust crate url to v2.5.4' (#229) from renovate/url-2.x-lockfile into master
Some checks failed
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #229
2025-04-21 23:56:48 +02:00
83de50409b
Fix locating Steam game path
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 23:55:55 +02:00
6e58449dac
fix(deps): update rust crate url to v2.5.4
All checks were successful
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
2025-04-21 21:46:13 +00:00
bf5c21dd03
chore(deps): update rust crate steamlocate to v2.0.1
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 21:46:09 +00:00
813b927da0
Merge pull request 'Miscellaneous changes' (#239) from feat/ci into master
Some checks failed
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #239
2025-04-21 23:34:39 +02:00
bb6396c932
Fix build script
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 23:15:08 +02:00
3d05a2395e
Fix clippy warnings 2025-04-21 23:13:36 +02:00
38a023bea6
Move serde_sjson to its own project 2025-04-21 22:52:54 +02:00
9ac13834a1
Commit lock file changes
Seems like Renovate missed this.
2025-04-21 18:45:18 +02:00
1a000371fa
Treat lint warnings as errors in CI
Warnings will not show up if they don't fail CI.
2025-04-21 18:41:59 +02:00
e33e02457b
Merge pull request 'chore(deps): update rust crate minijinja to v2.9.0' (#234) from renovate/minijinja-2.x-lockfile into master
Some checks failed
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #234
2025-04-21 18:37:01 +02:00
6aabf8f38b
Merge pull request 'fix(deps): update tokio-tracing monorepo' (#231) from renovate/tokio-tracing-monorepo into master
Some checks failed
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #231
2025-04-21 18:35:44 +02:00
3105edb6b8
Merge pull request 'fix(deps): update rust-futures monorepo to v0.3.31' (#230) from renovate/rust-futures-monorepo into master
Some checks failed
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #230
2025-04-21 18:35:31 +02:00
c3aec656c8
Merge pull request 'fix(deps): update rust crate serde to v1.0.219' (#225) from renovate/serde-monorepo into master
Reviewed-on: #225
2025-04-21 18:35:23 +02:00
95565b3f33
Merge pull request 'fix(deps): update rust crate serde_json to v1.0.140' (#226) from renovate/serde_json-1.x-lockfile into master
Reviewed-on: #226
2025-04-21 18:35:05 +02:00
e783646b02
Merge pull request 'fix(deps): update rust crate thiserror to v2.0.12' (#227) from renovate/thiserror-2.x-lockfile into master
Reviewed-on: #227
2025-04-21 18:34:50 +02:00
2ddbdf867a
Merge pull request 'chore(deps): update rust crate bitflags to v2.9.0' (#232) from renovate/bitflags-2.x-lockfile into master
Some checks failed
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #232
2025-04-21 18:33:52 +02:00
2ec26a1914
Merge pull request 'chore(deps): update rust crate fastrand to v2.3.0' (#233) from renovate/fastrand-2.x-lockfile into master
Reviewed-on: #233
2025-04-21 18:33:35 +02:00
98a5369cfa
Merge pull request 'chore(deps): update rust crate tempfile to v3.19.1' (#235) from renovate/tempfile-3.x-lockfile into master
Reviewed-on: #235
2025-04-21 18:33:22 +02:00
b0fbc445dd
Merge pull request 'chore(deps): update rust crate tokio to v1.44.2' (#236) from renovate/tokio-1.x-lockfile into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #236
2025-04-21 18:32:52 +02:00
198c098cde
Merge pull request 'chore(deps): update rust crate zip to v2.6.1' (#237) from renovate/zip-2.x-lockfile into master
Reviewed-on: #237
2025-04-21 18:32:38 +02:00
47cb71ebec
Merge pull request 'fix(deps): update rust crate regex to v1.11.1' (#238) from renovate/regex-1.x-lockfile into master
Reviewed-on: #238
2025-04-21 18:32:18 +02:00
be7b16c5ef
fix(deps): update rust crate regex to v1.11.1
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:47:56 +00:00
bdce3e7787
chore(deps): update rust crate zip to v2.6.1
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:47:48 +00:00
42e2eb7cc1
chore(deps): update rust crate tokio to v1.44.2
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:47:37 +00:00
221a6b8485
chore(deps): update rust crate tempfile to v3.19.1
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:47:28 +00:00
3f3c4aa321
chore(deps): update rust crate minijinja to v2.9.0
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:47:20 +00:00
c2f02e7ce6
chore(deps): update rust crate fastrand to v2.3.0
All checks were successful
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
2025-04-21 15:47:13 +00:00
be28b7045c
chore(deps): update rust crate bitflags to v2.9.0
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 15:47:03 +00:00
bcab6e697f
fix(deps): update tokio-tracing monorepo
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 15:46:56 +00:00
36fba192c0
fix(deps): update rust-futures monorepo to v0.3.31
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 15:46:46 +00:00
ccac8e3859
fix(deps): update rust crate thiserror to v2.0.12
All checks were successful
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
2025-04-21 15:46:21 +00:00
b119b0d38d
fix(deps): update rust crate serde_json to v1.0.140
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 15:46:14 +00:00
bbb701176f
fix(deps): update rust crate serde to v1.0.219
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 15:46:05 +00:00
341aa0a31e
Merge pull request 'chore(deps): update rust crate glob to v0.3.2' (#213) from renovate/glob-0.x-lockfile into master
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #213
2025-04-21 17:37:02 +02:00
a4430e27ec
Merge pull request 'chore(deps): update rust crate tokio-stream to v0.1.17' (#222) from renovate/tokio-stream-0.x-lockfile into master
All checks were successful
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #222
2025-04-21 17:36:51 +02:00
4fffcfbd60
Merge pull request 'fix(deps): update rust crate reqwest to v0.12.15' (#223) from renovate/reqwest-0.x-lockfile into master
Reviewed-on: #223
2025-04-21 17:36:39 +02:00
a0f676c0e6
Merge pull request 'Make PR pipelines public' (#224) from feature/ci into master
Reviewed-on: #224
2025-04-21 17:36:21 +02:00
e6744404a9
Make PR pipelines public 2025-04-21 17:35:58 +02:00
f2bf1493e5
fix(deps): update rust crate reqwest to v0.12.15
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:16:28 +00:00
64d9aa12aa
chore(deps): update rust crate tokio-stream to v0.1.17
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
2025-04-21 15:16:16 +00:00
86b8f9e40f
chore(deps): update rust crate glob to v0.3.2
All checks were successful
build/linux Build for the target platform: linux
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
2025-04-21 15:16:06 +00:00
435fc5fadb
Merge pull request 'chore(deps): update rust crate strip-ansi-escapes to v0.2.1' (#218) from renovate/strip-ansi-escapes-0.x-lockfile into master
Some checks are pending
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #218
2025-04-21 17:15:22 +02:00
9b21fbf493
Merge pull request 'chore(deps): update rust crate open to v5.3.2' (#215) from renovate/open-5.x-lockfile into master
Some checks failed
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #215
2025-04-21 17:03:12 +02:00
ab597505ae
Merge pull request 'chore(deps): update rust crate pin-project-lite to v0.2.16' (#216) from renovate/pin-project-lite-0.x-lockfile into master
Reviewed-on: #216
2025-04-21 17:02:52 +02:00
1e1fa54a57
Merge pull request 'chore(deps): update rust crate interprocess to v2.2.3' (#214) from renovate/interprocess-2.x-lockfile into master
Some checks are pending
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #214
2025-04-21 17:00:39 +02:00
c9df8df801
Merge pull request 'chore(deps): update rust crate bindgen to v0.71.1' (#211) from renovate/bindgen-0.x-lockfile into master
Reviewed-on: #211
2025-04-21 17:00:21 +02:00
a7f0ec50ab
Merge pull request 'chore(deps): update rust crate clap to v4.5.37' (#212) from renovate/clap-4.x-lockfile into master
Reviewed-on: #212
2025-04-21 17:00:08 +02:00
b19225c75a
chore(deps): update rust crate strip-ansi-escapes to v0.2.1
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 13:42:48 +00:00
f1291bdf33
chore(deps): update rust crate pin-project-lite to v0.2.16
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2025-04-21 13:42:40 +00:00
9e209add0c
chore(deps): update rust crate open to v5.3.2
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 13:42:36 +00:00
5fa6ecfd0d
chore(deps): update rust crate interprocess to v2.2.3
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-04-21 13:42:33 +00:00
96c0953cf9
chore(deps): update rust crate clap to v4.5.37
All checks were successful
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
2025-04-21 13:42:24 +00:00
1b56acfd63
chore(deps): update rust crate bindgen to v0.71.1
All checks were successful
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
2025-04-21 13:42:19 +00:00
a22fc9c1ea
Merge pull request 'Fix more URLs in CI pipelines' (#221) from feature/ci into master
Some checks are pending
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #221
2025-04-21 15:34:04 +02:00
c2207c22ef
Fix more URLs in CI pipelines 2025-04-21 15:33:33 +02:00
c6f9e2a369
Merge pull request 'Fix incorrect URLs' (#220) from feature/ci into master
Some checks are pending
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Reviewed-on: #220
2025-04-21 15:29:00 +02:00
5aa8421f7d
Fix incorrect URLs 2025-04-21 15:28:27 +02:00
df79c59dc2
Merge pull request 'Remove internal URLs from CI' (#219) from feature/ci into master
Reviewed-on: #219
2025-04-21 15:21:44 +02:00
e61a252ee6
Remove internal URLs from CI
Due to using internal URLs, the pipelines demanded a very specific
network setup to work.
By changing everything to their public-facing URLs, they now become
agnostic to internal topology.
2025-04-21 15:18:19 +02:00
8cb2c6b2cd
Merge pull request 'Fix using branch for version number' (#209) from issue/205 into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #209
2025-03-12 12:25:49 +00:00
6ba13ac1ec
Fix using branch for version number 2025-03-12 13:24:03 +01:00
4d0762c0ba
Merge pull request 'Fix branch name in package version' (#207) from issue/205 into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #207
2025-03-12 12:15:30 +00:00
752291fe2d
Merge pull request 'Explicitly define base branches' (#208) from feat/renovate into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #208
2025-03-12 12:15:09 +00:00
71f945a96c
Explicitly define base branches
Currently, the dependency dashboard lists a bunch of pending updates
under a section called "Other branches". I'm not sure, but this sounds
like one of the configs I extend from enables base branches other than
only the default. To test, and make it explicit, set only the branches
I really want checked.

I'm adding the `release/.*` regex for now, even though I don't have
any release process yet.
2025-03-12 13:11:01 +01:00
d15f533e19
Fix branch name in package version
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-03-12 11:52:53 +01:00
1a3c564ecf
Merge pull request 'Improve CI artifact version names' (#206) from issue/205 into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #206
2025-03-12 10:46:26 +00:00
beba47f340
Push a packaged with a fixed version for master
All checks were successful
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
To provide something that can easily be linked to, also push packages
built from `master` to a version that doesn't contain the SHA.
2025-03-12 11:33:48 +01:00
5612e271fb
Improve version name for CI artifacts built off master
The name from `git describe --tags` is rather confusing to people that
aren't familiar with it. Especially in the current situation, where
there are no proper versioned releases.

A name like `master-123456` should be much clearer.

Closes #205.
2025-03-12 11:26:24 +01:00
69300e87e6
Merge pull request 'fix(deps): update rust crate thiserror to v2' (#200) from renovate/thiserror-2.x into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #200
2025-02-19 10:08:51 +00:00
a3583b4485
fix(deps): update rust crate thiserror to v2
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-02-19 11:02:02 +01:00
5982a66033
Merge pull request 'chore(deps): update rust crate notify to v8' (#202) from renovate/notify-8.x into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #202
2025-02-19 09:53:28 +00:00
adf9610ecc
chore(deps): update rust crate notify to v8
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2025-01-10 14:48:31 +00:00
91cd54fff7
Merge pull request 'chore(deps): update rust crate bindgen to 0.71.0' (#201) from renovate/bindgen-0.x into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #201
2024-12-10 10:05:20 +00:00
b219e20f3a
chore(deps): update rust crate bindgen to 0.71.0
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-12-06 20:32:37 +00:00
f9ccdf746e
Merge pull request 'chore(deps): update rust crate notify to v7' (#199) from renovate/notify-7.x into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #199
2024-10-28 09:24:47 +00:00
72ce06b0e5
chore(deps): update rust crate notify to v7
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-10-25 17:32:26 +00:00
fc151f1449
Merge pull request 'fix(deps): update rust crate serde to v1.0.209' (#194) from renovate/serde-monorepo into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #194
2024-08-27 07:07:26 +00:00
659b63bfe9
fix(deps): update rust crate serde to v1.0.209
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-08-27 06:30:39 +00:00
9f90b45275
Merge pull request 'chore(deps): update rust crate minijinja to v2.2.0' (#195) from renovate/minijinja-2.x-lockfile into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #195
2024-08-27 06:20:36 +00:00
67c64bb357
chore(deps): update rust crate minijinja to v2.2.0
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-08-26 20:45:40 +00:00
6017ec058b
Merge pull request 'chore(deps): update rust crate fastrand to v2.1.1' (#193) from renovate/fastrand-2.x-lockfile into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/193
2024-08-24 14:09:14 +00:00
ffd4927d27
chore(deps): update rust crate fastrand to v2.1.1
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2024-08-24 10:02:43 +00:00
49a9eb4312
Merge pull request 'fix(deps): update rust crate serde_json to v1.0.127' (#192) from renovate/serde_json-1.x-lockfile into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/192
2024-08-24 09:48:42 +00:00
4d665200fa
fix(deps): update rust crate serde_json to v1.0.127
All checks were successful
build/msvc Build for the target platform: msvc
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
2024-08-23 21:30:32 +00:00
b3463ffb46
Merge pull request 'chore: Configure Renovate' (#187) from renovate/configure into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/187
2024-08-21 13:31:35 +00:00
7cb44532b2
Add .renovaterc
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-08-21 15:28:24 +02:00
15aa9bcf5e
Merge pull request 'Update dependencies' (#188) from feat/dependencies into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/188
2024-08-21 13:28:11 +00:00
a2bbab1398
Update dependencies
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
2024-08-21 14:33:39 +02:00
88becb72a9
Merge pull request 'Consilidate template libraries' (#186) from issue/124 into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/186
2024-08-21 09:49:18 +00:00
df2992a476
Improve mod template comments
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2024-08-20 16:28:56 +02:00
e336240094
Consilidate template libraries
Remove last uses of `string_template` in favor of `minijinja`.

Closes #124.
2024-08-20 16:28:08 +02:00
d7fa80f471
Merge pull request 'Add tests for hash inversion' (#185) from feat/murmur-tests into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: https://git.sclu1034.dev///bitsquid_dt/dtmt/pulls/185
2024-08-14 12:07:31 +00:00
831592edf6
Merge pull request 'Implement bundle database resource hashes' (#184) from feat/bundle-database into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #184
2024-08-14 13:58:38 +02:00
2a1d8d815f
Add tests for hash inversion
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Just a quick round trip test, and an additional assert to demonstrate
that byte order does matter.
2024-08-14 09:22:24 +02:00
d931e6b9ca
dtmt: Add command to search for files
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
Not really of much use at the moment, but inspired by the HD2 community.
2024-07-28 22:04:14 +02:00
7fa08c2efd
dtmt: Implement listing bundle database contents 2024-07-28 22:03:43 +02:00
dbf060032b
sdk: Implement bundle database resource hashes
Algorithm reverse engineered by WhiteGoat.
2024-07-28 14:46:10 +02:00
4b39d290b6 Merge pull request 'Add IdString32' (#183) from feat/various into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #183
2024-07-19 11:41:43 +02:00
3a6e954f9a
sdk: Refactor murmur modules and add IdString32
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2024-07-19 11:30:09 +02:00
5a880b2953 Merge pull request 'Various minor changes extracted from unfinished projects' (#182) from feat/various into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #182
2024-07-19 11:13:47 +02:00
f1f9a818cc
sdk: Allow any byte stream for hashing dictionary entries
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/linux Build for the target platform: linux
build/msvc Build for the target platform: msvc
2024-07-19 09:48:25 +02:00
c997489e18
Add some doc comments 2024-07-19 09:48:23 +02:00
08219f05ba
sdk: Fix reading strings
Fatshark has a few weird string fields, where they provide a length
field, but then sometimes write a shorter, NUL-terminated string into
that same field and adding padding up to the "advertised" length.
To properly read those strings, we can't rely on just the length field
anymore, but need to check for a NUL, too.
2024-07-19 09:48:21 +02:00
edad0d4493
Improve file listing output
Adds pretty printing for file size and always shows the bundle hash name
2024-07-19 09:48:20 +02:00
74a7aaa6e5
dtmt-shared: Write log lines to stderr
Ideally, I would prefer the usual split per logging level, but that
seems to be somewhat complex with `tracing_subscriber`, so this simply
switches everything over to stderr, so that some of the experiment
commands can write results to stdout.
2024-07-19 09:48:15 +02:00
437e724d07 Merge pull request 'Implement name overrides' (#181) from feat/name-overrides into master
All checks were successful
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
Reviewed-on: #181
2024-07-19 09:35:16 +02:00
95fc6c160b
dtmt: Implement name overrides
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
For most of the game files, we don't know the actual name, only the hash
of that name. To still allow building bundles that contain files with
that name (e.g. to override a game file with a custom one), there needs
to be a way to tell DTMT to name a file such that its hash is the same
as the one in the game.
The initial idea was to just expect the file name on disk to be the
hash, but that wouldn't allow for arbitrary folder structures anymore.

So instead, there is now a new, optional setting in `dtmt.cfg`, where
the modder can map a file path to an override name.
2024-07-18 09:50:48 +02:00
b7e26eee57
refactor(sdk): Split BundleFileType into its own file 2024-07-17 11:14:22 +02:00
54 changed files with 2588 additions and 1464 deletions

View file

@ -6,24 +6,30 @@ resource_types:
- name: gitea-package - name: gitea-package
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/gitea-package repository: registry.sclu1034.dev/gitea-package
username: ((registry_user))
password: ((registry_password))
- name: gitea-status - name: gitea-status
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/gitea-status repository: registry.sclu1034.dev/gitea-status
username: ((registry_user))
password: ((registry_password))
- name: gitea-pr - name: gitea-pr
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/gitea-pr repository: registry.sclu1034.dev/gitea-pr
username: ((registry_user))
password: ((registry_password))
resources: resources:
- name: repo - name: repo
type: git type: git
source: source:
uri: http://forgejo:3000/bitsquid_dt/dtmt uri: https://git.sclu1034.dev/bitsquid_dt/dtmt
branch: master branch: master
- name: repo-pr - name: repo-pr
@ -38,7 +44,7 @@ resources:
type: gitea-package type: gitea-package
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
type: generic type: generic
name: dtmt name: dtmt
@ -48,7 +54,7 @@ resources:
type: gitea-status type: gitea-status
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
repo: dtmt repo: dtmt
context: build/msvc context: build/msvc
@ -58,7 +64,7 @@ resources:
type: gitea-status type: gitea-status
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
repo: dtmt repo: dtmt
context: build/linux context: build/linux
@ -82,9 +88,12 @@ jobs:
values: ((.:prs)) values: ((.:prs))
set_pipeline: dtmt-pr set_pipeline: dtmt-pr
file: repo/.ci/pipelines/pr.yml file: repo/.ci/pipelines/pr.yml
public: true
vars: vars:
pr: ((.:pr)) pr: ((.:pr))
gitea_api_key: ((gitea_api_key)) gitea_api_key: ((gitea_api_key))
registry_user: ((registry_user))
registry_password: ((registry_password))
instance_vars: instance_vars:
number: ((.:pr.number)) number: ((.:pr.number))
@ -125,8 +134,8 @@ jobs:
vars: vars:
pr: "" pr: ""
target: msvc target: msvc
gitea_url: http://forgejo:3000 registry_user: ((registry_user))
gitea_api_key: ((gitea_api_key)) registry_password: ((registry_password))
- load_var: version_number - load_var: version_number
reveal: true reveal: true
@ -142,10 +151,21 @@ jobs:
fail_fast: true fail_fast: true
override: true override: true
globs: globs:
- artifact/dtmt
- artifact/dtmm
- artifact/*.exe - artifact/*.exe
- artifact/*.sha256 - artifact/*.exe.sha256
- put: package
resource: gitea-package
no_get: true
inputs:
- artifact
params:
version: master
fail_fast: true
override: true
globs:
- artifact/*.exe
- artifact/*.exe.sha256
- name: build-linux - name: build-linux
on_success: on_success:
@ -183,8 +203,10 @@ jobs:
vars: vars:
pr: "" pr: ""
target: linux target: linux
gitea_url: http://forgejo:3000 gitea_url: https://git.sclu1034.dev
gitea_api_key: ((gitea_api_key)) gitea_api_key: ((gitea_api_key))
registry_user: ((registry_user))
registry_password: ((registry_password))
- load_var: version_number - load_var: version_number
reveal: true reveal: true
@ -202,5 +224,20 @@ jobs:
globs: globs:
- artifact/dtmt - artifact/dtmt
- artifact/dtmm - artifact/dtmm
- artifact/*.exe - artifact/dtmm.sha256
- artifact/*.sha256 - artifact/dtmt.sha256
- put: package
resource: gitea-package
no_get: true
inputs:
- artifact
params:
version: master
fail_fast: true
override: true
globs:
- artifact/dtmt
- artifact/dtmm
- artifact/dtmm.sha256
- artifact/dtmt.sha256

View file

@ -18,6 +18,8 @@ jobs:
file: repo/.ci/tasks/build.yml file: repo/.ci/tasks/build.yml
vars: vars:
target: msvc target: msvc
registry_user: ((registry_user))
registry_password: ((registry_password))
- name: build-linux - name: build-linux
plan: plan:
- get: repo - get: repo
@ -26,3 +28,5 @@ jobs:
file: repo/.ci/tasks/build.yml file: repo/.ci/tasks/build.yml
vars: vars:
target: linux target: linux
registry_user: ((registry_user))
registry_password: ((registry_password))

View file

@ -6,26 +6,30 @@ resource_types:
- name: gitea-package - name: gitea-package
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/gitea-package repository: registry.sclu1034.dev/gitea-package
username: ((registry_user))
password: ((registry_password))
- name: gitea-status - name: gitea-status
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/gitea-status repository: registry.sclu1034.dev/gitea-status
username: ((registry_user))
password: ((registry_password))
resources: resources:
- name: repo - name: repo
type: git type: git
source: source:
uri: http://forgejo:3000/bitsquid_dt/dtmt uri: https://git.sclu1034.dev/bitsquid_dt/dtmt
branch: ((pr.head.ref)) branch: ((pr.head.ref))
- name: gitea-package - name: gitea-package
type: gitea-package type: gitea-package
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
type: generic type: generic
name: dtmt name: dtmt
@ -34,7 +38,7 @@ resources:
type: gitea-status type: gitea-status
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
repo: dtmt repo: dtmt
context: lint/clippy context: lint/clippy
@ -44,7 +48,7 @@ resources:
type: gitea-status type: gitea-status
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
repo: dtmt repo: dtmt
context: build/msvc context: build/msvc
@ -54,7 +58,7 @@ resources:
type: gitea-status type: gitea-status
source: source:
access_token: ((gitea_api_key)) access_token: ((gitea_api_key))
url: http://forgejo:3000 url: https://git.sclu1034.dev
owner: bitsquid_dt owner: bitsquid_dt
repo: dtmt repo: dtmt
context: build/linux context: build/linux
@ -97,6 +101,8 @@ jobs:
file: repo/.ci/tasks/clippy.yml file: repo/.ci/tasks/clippy.yml
vars: vars:
gitea_api_key: ((gitea_api_key)) gitea_api_key: ((gitea_api_key))
registry_user: ((registry_user))
registry_password: ((registry_password))
- name: build-msvc - name: build-msvc
@ -135,8 +141,10 @@ jobs:
vars: vars:
target: msvc target: msvc
pr: ((pr)) pr: ((pr))
gitea_url: http://forgejo:3000 gitea_url: https://git.sclu1034.dev
gitea_api_key: ((gitea_api_key)) gitea_api_key: ((gitea_api_key))
registry_user: ((registry_user))
registry_password: ((registry_password))
- load_var: version_number - load_var: version_number
reveal: true reveal: true
@ -193,8 +201,10 @@ jobs:
vars: vars:
target: linux target: linux
pr: ((pr)) pr: ((pr))
gitea_url: http://forgejo:3000 gitea_url: https://git.sclu1034.dev
gitea_api_key: ((gitea_api_key)) gitea_api_key: ((gitea_api_key))
registry_user: ((registry_user))
registry_password: ((registry_password))
- load_var: version_number - load_var: version_number
reveal: true reveal: true

View file

@ -20,12 +20,15 @@ install_artifact() {
cd "repo" cd "repo"
PR=${PR:-} PR=${PR:-}
ref=$(cat .git/ref || echo "HEAD")
if [ -n "$PR" ]; then if [ -n "$PR" ]; then
title "PR: $(echo "$PR" | jq '.number') - $(echo "$PR" | jq '.title')" title "PR: $(echo "$PR" | jq '.number') - $(echo "$PR" | jq '.title')"
ref="pr-$(echo "$PR" | jq '.number')-$(git rev-parse --short "$(cat .git/ref || echo "HEAD")" 2>/dev/null || echo 'manual')" ref="pr-$(echo "$PR" | jq '.number')-$(git rev-parse --short "$ref" 2>/dev/null || echo 'manual')"
elif [ -f ".git/branch" ]; then
ref=$(cat .git/branch)-$(git rev-parse --short "$ref")
else else
ref=$(git describe --tags) ref=$(git rev-parse --short "$ref")
fi fi
title "Version: '$ref'" title "Version: '$ref'"

View file

@ -6,7 +6,9 @@ image_resource:
name: ctmt-bi-base-((target)) name: ctmt-bi-base-((target))
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/dtmt-ci-base-((target)) repository: registry.sclu1034.dev/dtmt-ci-base-((target))
username: ((registry_user))
password: ((registry_password))
tag: latest tag: latest
inputs: inputs:
@ -22,7 +24,6 @@ caches:
params: params:
CI: "true" CI: "true"
TARGET: ((target)) TARGET: ((target))
GITEA_API_KEY: ((gitea_api_key))
PR: ((pr)) PR: ((pr))
OUTPUT: artifact OUTPUT: artifact

View file

@ -10,6 +10,6 @@ title "Install clippy"
rustup component add clippy rustup component add clippy
title "Run clippy" title "Run clippy"
cargo clippy --color always --no-deps cargo clippy --color always --no-deps -- -D warnings
title "Done" title "Done"

View file

@ -6,7 +6,9 @@ image_resource:
name: dtmt-ci-base-linux name: dtmt-ci-base-linux
type: registry-image type: registry-image
source: source:
repository: registry.local:5000/dtmt-ci-base-linux repository: registry.sclu1034.dev/dtmt-ci-base-linux
username: ((registry_user))
password: ((registry_password))
tag: latest tag: latest
inputs: inputs:

3
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "lib/serde_sjson"]
path = lib/serde_sjson
url = https://git.sclu1034.dev/lucas/serde_sjson.git
[submodule "lib/luajit2-sys"] [submodule "lib/luajit2-sys"]
path = lib/luajit2-sys path = lib/luajit2-sys
url = https://github.com/sclu1034/luajit2-sys.git url = https://github.com/sclu1034/luajit2-sys.git

15
.renovaterc Normal file
View file

@ -0,0 +1,15 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":combinePatchMinorReleases",
":enableVulnerabilityAlerts",
":rebaseStalePrs"
],
"prConcurrentLimit": 10,
"branchPrefix": "renovate/",
"baseBranches": [
"$default",
"/^release\\/.*/"
]
}

View file

@ -20,6 +20,8 @@
- dtmm: fetch file version for Nexus mods - dtmm: fetch file version for Nexus mods
- dtmm: handle `nxm://` URIs via IPC and import the corresponding mod - dtmm: handle `nxm://` URIs via IPC and import the corresponding mod
- dtmm: Add button to open mod on nexusmods.com - dtmm: Add button to open mod on nexusmods.com
- dtmt: Implement commands to list bundles and contents
- dtmt: Implement command to search for files
=== Fixed === Fixed

1663
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,18 +6,57 @@ members = [
"lib/dtmt-shared", "lib/dtmt-shared",
"lib/oodle", "lib/oodle",
"lib/sdk", "lib/sdk",
"lib/serde_sjson",
"lib/luajit2-sys", "lib/luajit2-sys",
"lib/color-eyre", "lib/color-eyre",
] ]
exclude = ["lib/color-eyre"] exclude = ["lib/color-eyre"]
[workspace.dependencies] [workspace.dependencies]
zip = { version = "2.1.3", default-features = false, features = ["deflate", "bzip2", "zstd", "time"] } ansi-parser = "0.9.1"
ansi_term = "0.12.1"
[patch.crates-io] async-recursion = "1.0.5"
bincode = "2.0.0"
bitflags = "2.5.0"
byteorder = "1.4.3"
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "string", "unicode"] }
cli-table = { version = "0.5.0", default-features = false, features = ["derive"] }
color-eyre = { path = "lib/color-eyre" } color-eyre = { path = "lib/color-eyre" }
ansi-parser = { git = "https://gitlab.com/lschwiderski/ansi-parser.git", branch = "issue/outdated-heapless", version = "0.9.1" } colors-transform = "0.2.11"
confy = "0.6.1"
csv-async = { version = "1.2.4", features = ["tokio", "serde"] }
druid = { version = "0.8", features = ["im", "serde", "image", "png", "jpeg", "bmp", "webp", "svg"] }
druid-widget-nursery = "0.1"
dtmt-shared = { path = "lib/dtmt-shared" }
fastrand = "2.1.0"
futures = "0.3.25"
futures-util = "0.3.24"
glob = "0.3.0"
interprocess = "2.1.0"
lazy_static = "1.4.0"
luajit2-sys = { path = "lib/luajit2-sys" }
minijinja = { version = "2.0.1", default-features = false, features = ["serde"] }
nanorand = "0.7.0"
nexusmods = { path = "lib/nexusmods" }
notify = "8.0.0"
oodle = { path = "lib/oodle" }
open = "5.0.1"
path-clean = "1.0.1"
path-slash = "0.2.1"
pin-project-lite = "0.2.9"
promptly = "0.3.1"
sdk = { path = "lib/sdk" }
serde = { version = "1.0.152", features = ["derive", "rc"] }
serde_sjson = "1.2.1"
steamlocate = "2.0.0-beta.2"
strip-ansi-escapes = "0.2.0"
time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-offset", "formatting", "macros"] }
tokio = { version = "1.23.0", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
tokio-stream = { version = "0.1.12", features = ["fs", "io-util"] }
tracing = { version = "0.1.37", features = ["async-await"] }
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
usvg = "0.25.0"
zip = { version = "2.1.3", default-features = false, features = ["deflate", "bzip2", "zstd", "time"] }
[profile.dev.package.backtrace] [profile.dev.package.backtrace]
opt-level = 3 opt-level = 3

View file

@ -40,6 +40,8 @@ set-base-pipeline:
--pipeline dtmt \ --pipeline dtmt \
--config .ci/pipelines/base.yml \ --config .ci/pipelines/base.yml \
-v gitea_api_key=${GITEA_API_KEY} \ -v gitea_api_key=${GITEA_API_KEY} \
-v registry_user=${REGISTRY_USER} \
-v registry_password=${REGISTRY_PASSWORD} \
-v owner=bitsquid_dt \ -v owner=bitsquid_dt \
-v repo=dtmt -v repo=dtmt
@ -48,7 +50,7 @@ set-pr-pipeline pr:
-H "Authorization: ${GITEA_API_KEY}" \ -H "Authorization: ${GITEA_API_KEY}" \
-H 'Accept: application/json' \ -H 'Accept: application/json' \
'https://git.sclu1034.dev/api/v1/repos/bitsquid_dt/dtmt/pulls/{{pr}}' \ 'https://git.sclu1034.dev/api/v1/repos/bitsquid_dt/dtmt/pulls/{{pr}}' \
| yq -y '.' - > 'pr-{{pr}}.yaml' | yq -y '.' - > 'pr-{{pr}}.yaml'
fly -t main set-pipeline \ fly -t main set-pipeline \
--pipeline dtmt-pr \ --pipeline dtmt-pr \
--config .ci/pipelines/pr.yml \ --config .ci/pipelines/pr.yml \

View file

@ -12,37 +12,37 @@ license-file = "LICENSE"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ansi-parser = "0.9.0" ansi-parser = { workspace = true }
async-recursion = "1.0.5" async-recursion = { workspace = true }
bincode = "1.3.3" bincode = { workspace = true }
bitflags = "2.5.0" bitflags = { workspace = true }
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "string", "unicode"] } clap = { workspace = true }
color-eyre = "0.6.2" color-eyre = { workspace = true }
colors-transform = "0.2.11" colors-transform = { workspace = true }
confy = "0.6.1" confy = { workspace = true }
druid = { version = "0.8", features = ["im", "serde", "image", "png", "jpeg", "bmp", "webp", "svg"] } druid = { workspace = true }
druid-widget-nursery = "0.1" druid-widget-nursery = { workspace = true }
dtmt-shared = { path = "../../lib/dtmt-shared", version = "*" } dtmt-shared = { workspace = true }
futures = "0.3.25" futures = { workspace = true }
interprocess = "2.1.0" interprocess = { workspace = true }
lazy_static = "1.4.0" lazy_static = { workspace = true }
luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" } luajit2-sys = { workspace = true }
minijinja = { version = "2.0.1", default-features = false } minijinja = { workspace = true }
nexusmods = { path = "../../lib/nexusmods", version = "*" } nexusmods = { workspace = true }
oodle = { path = "../../lib/oodle", version = "*" } oodle = { workspace = true }
open = "5.0.1" open = { workspace = true }
path-slash = "0.2.1" path-slash = { workspace = true }
sdk = { path = "../../lib/sdk", version = "*" } sdk = { workspace = true }
serde = { version = "1.0.152", features = ["derive", "rc"] } serde = { workspace = true }
serde_sjson = { path = "../../lib/serde_sjson", version = "*" } serde_sjson = { workspace = true }
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = { workspace = true }
time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-offset"] } time = { workspace = true }
tokio = { version = "1.23.0", features = ["rt", "fs", "tracing", "sync"] } tokio = { workspace = true }
tokio-stream = { version = "0.1.12", features = ["fs"] } tokio-stream = { workspace = true }
tracing = "0.1.37" tracing = { workspace = true }
tracing-error = "0.2.0" tracing-error = { workspace = true }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { workspace = true }
usvg = "0.25.0" usvg = { workspace = true }
zip = { workspace = true } zip = { workspace = true }
[build-dependencies] [build-dependencies]

View file

@ -116,14 +116,14 @@ async fn patch_game_settings(state: Arc<ActionState>) -> Result<()> {
eyre::bail!("couldn't find 'boot_script' field"); eyre::bail!("couldn't find 'boot_script' field");
}; };
f.write_all(settings[0..i].as_bytes()).await?; f.write_all(&settings.as_bytes()[0..i]).await?;
f.write_all(b"boot_script = \"scripts/mod_main\"").await?; f.write_all(b"boot_script = \"scripts/mod_main\"").await?;
let Some(j) = settings[i..].find('\n') else { let Some(j) = settings[i..].find('\n') else {
eyre::bail!("couldn't find end of 'boot_script' field"); eyre::bail!("couldn't find end of 'boot_script' field");
}; };
f.write_all(settings[(i + j)..].as_bytes()).await?; f.write_all(&settings.as_bytes()[(i + j)..]).await?;
Ok(()) Ok(())
} }
@ -324,11 +324,11 @@ async fn build_bundles(state: Arc<ActionState>) -> Result<Vec<Bundle>> {
let mut bundles = Vec::new(); let mut bundles = Vec::new();
let mut add_lua_asset = |name, data: &str| { let mut add_lua_asset = |name: &str, data: &str| {
let span = tracing::info_span!("Compiling Lua", name, data_len = data.len()); let span = tracing::info_span!("Compiling Lua", name, data_len = data.len());
let _enter = span.enter(); let _enter = span.enter();
let file = lua::compile(name, data).wrap_err("Failed to compile Lua")?; let file = lua::compile(name.to_string(), data).wrap_err("Failed to compile Lua")?;
mod_bundle.add_file(file); mod_bundle.add_file(file);
@ -453,10 +453,7 @@ async fn build_bundles(state: Arc<ActionState>) -> Result<Vec<Bundle>> {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn patch_boot_bundle( async fn patch_boot_bundle(state: Arc<ActionState>, deployment_info: &str) -> Result<Vec<Bundle>> {
state: Arc<ActionState>,
deployment_info: &String,
) -> Result<Vec<Bundle>> {
let bundle_dir = Arc::new(state.game_dir.join("bundle")); let bundle_dir = Arc::new(state.game_dir.join("bundle"));
let bundle_path = bundle_dir.join(format!("{:x}", Murmur64::hash(BOOT_BUNDLE_NAME.as_bytes()))); let bundle_path = bundle_dir.join(format!("{:x}", Murmur64::hash(BOOT_BUNDLE_NAME.as_bytes())));
@ -517,8 +514,8 @@ async fn patch_boot_bundle(
.wrap_err("Failed to render template `mod_main.lua`")?; .wrap_err("Failed to render template `mod_main.lua`")?;
tracing::trace!("Main script rendered:\n===========\n{}\n=============", lua); tracing::trace!("Main script rendered:\n===========\n{}\n=============", lua);
let file = let file = lua::compile(MOD_BOOT_SCRIPT.to_string(), lua)
lua::compile(MOD_BOOT_SCRIPT, lua).wrap_err("Failed to compile mod main Lua file")?; .wrap_err("Failed to compile mod main Lua file")?;
boot_bundle.add_file(file); boot_bundle.add_file(file);
} }
@ -590,11 +587,7 @@ fn build_deployment_data(
.map(|bundle| format!("{:x}", bundle.name().to_murmur64())) .map(|bundle| format!("{:x}", bundle.name().to_murmur64()))
.collect(), .collect(),
// TODO: // TODO:
mod_folders: mod_folders mod_folders: mod_folders.as_ref().to_vec(),
.as_ref()
.iter()
.map(|folder| folder.clone())
.collect(),
}; };
serde_sjson::to_string(&info).wrap_err("Failed to serizalize deployment data") serde_sjson::to_string(&info).wrap_err("Failed to serizalize deployment data")
} }

View file

@ -91,14 +91,14 @@ async fn patch_game_settings(state: Arc<ActionState>) -> Result<()> {
eyre::bail!("couldn't find 'boot_script' field"); eyre::bail!("couldn't find 'boot_script' field");
}; };
f.write_all(settings[0..i].as_bytes()).await?; f.write_all(&settings.as_bytes()[0..i]).await?;
f.write_all(b"boot_script = \"scripts/mod_main\"").await?; f.write_all(b"boot_script = \"scripts/mod_main\"").await?;
let Some(j) = settings[i..].find('\n') else { let Some(j) = settings[i..].find('\n') else {
eyre::bail!("couldn't find end of 'boot_script' field"); eyre::bail!("couldn't find end of 'boot_script' field");
}; };
f.write_all(settings[(i + j)..].as_bytes()).await?; f.write_all(&settings.as_bytes()[(i + j)..]).await?;
Ok(()) Ok(())
} }
@ -208,7 +208,7 @@ pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> {
for p in paths { for p in paths {
let path = bundle_dir.join(p); let path = bundle_dir.join(p);
let backup = bundle_dir.join(&format!("{}.bak", p)); let backup = bundle_dir.join(format!("{}.bak", p));
let res = async { let res = async {
tracing::debug!( tracing::debug!(

View file

@ -297,6 +297,7 @@ fn extract_mod_config<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<(Mo
packages: Vec::new(), packages: Vec::new(),
resources, resources,
depends: Vec::new(), depends: Vec::new(),
name_overrides: Default::default(),
}; };
Ok((cfg, root)) Ok((cfg, root))
@ -396,7 +397,7 @@ fn extract_legacy_mod<R: Read + Seek>(
tracing::trace!("Writing file '{}'", name.display()); tracing::trace!("Writing file '{}'", name.display());
let mut out = std::fs::OpenOptions::new() let mut out = std::fs::OpenOptions::new()
.write(true) .write(true)
.create(true) .truncate(true)
.open(&name) .open(&name)
.wrap_err_with(|| format!("Failed to open file '{}'", name.display()))?; .wrap_err_with(|| format!("Failed to open file '{}'", name.display()))?;

View file

@ -52,10 +52,14 @@ fn notify_nxm_download(
tracing::debug!("Connected to main process at '{}'", IPC_ADDRESS); tracing::debug!("Connected to main process at '{}'", IPC_ADDRESS);
bincode::serialize_into(&mut stream, uri.as_ref()).wrap_err("Failed to send URI")?; let bincode_config = bincode::config::standard();
bincode::encode_into_std_write(uri.as_ref(), &mut stream, bincode_config)
.wrap_err("Failed to send URI")?;
// We don't really care what the message is, we just need an acknowledgement. // We don't really care what the message is, we just need an acknowledgement.
let _: String = bincode::deserialize_from(&mut stream).wrap_err("Failed to receive reply")?; let _: String = bincode::decode_from_std_read(&mut stream, bincode_config)
.wrap_err("Failed to receive reply")?;
tracing::info!( tracing::info!(
"Notified DTMM with uri '{}'. Check the main window.", "Notified DTMM with uri '{}'. Check the main window.",
@ -160,22 +164,33 @@ fn main() -> Result<()> {
match res { match res {
Ok(mut stream) => { Ok(mut stream) => {
let res = bincode::deserialize_from(&mut stream) let res = bincode::decode_from_std_read(
.wrap_err("Failed to read message") &mut stream,
.and_then(|uri: String| { bincode::config::standard(),
tracing::trace!(uri, "Received NXM uri"); )
.wrap_err("Failed to read message")
.and_then(|uri: String| {
tracing::trace!(uri, "Received NXM uri");
event_sink event_sink
.submit_command(ACTION_HANDLE_NXM, uri, druid::Target::Auto) .submit_command(ACTION_HANDLE_NXM, uri, druid::Target::Auto)
.wrap_err("Failed to start NXM download") .wrap_err("Failed to start NXM download")
}); });
match res { match res {
Ok(()) => { Ok(()) => {
let _ = bincode::serialize_into(&mut stream, "Ok"); let _ = bincode::encode_into_std_write(
"Ok",
&mut stream,
bincode::config::standard(),
);
} }
Err(err) => { Err(err) => {
tracing::error!("{:?}", err); tracing::error!("{:?}", err);
let _ = bincode::serialize_into(&mut stream, "Error"); let _ = bincode::encode_into_std_write(
"Error",
&mut stream,
bincode::config::standard(),
);
} }
} }
} }

View file

@ -4,34 +4,36 @@ version = "0.3.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "unicode"] } async-recursion = { workspace = true }
cli-table = { version = "0.4.7", default-features = false, features = ["derive"] } clap = { workspace = true }
color-eyre = "0.6.2" cli-table = { workspace = true }
confy = "0.6.1" color-eyre = { workspace = true }
csv-async = { version = "1.2.4", features = ["tokio", "serde"] } confy = { workspace = true }
dtmt-shared = { path = "../../lib/dtmt-shared", version = "*" } csv-async = { workspace = true }
futures = "0.3.25" dtmt-shared = { workspace = true }
futures-util = "0.3.24" futures = { workspace = true }
glob = "0.3.0" futures-util = { workspace = true }
nanorand = "0.7.0" glob = { workspace = true }
oodle = { path = "../../lib/oodle", version = "*" } luajit2-sys = { workspace = true }
pin-project-lite = "0.2.9" minijinja = { workspace = true }
promptly = "0.3.1" nanorand = { workspace = true }
sdk = { path = "../../lib/sdk", version = "*" } notify = { workspace = true }
serde_sjson = { path = "../../lib/serde_sjson", version = "*" } oodle = { workspace = true }
serde = { version = "1.0.147", features = ["derive"] } path-clean = { workspace = true }
string_template = "0.2.1" path-slash = { workspace = true }
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] } pin-project-lite = { workspace = true }
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] } promptly = { workspace = true }
tracing-error = "0.2.0" sdk = { workspace = true }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } serde = { workspace = true }
tracing = { version = "0.1.37", features = ["async-await"] } serde_sjson = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
tracing-error = { workspace = true }
tracing-subscriber = { workspace = true }
zip = { workspace = true } zip = { workspace = true }
path-clean = "1.0.1"
path-slash = "0.2.1" # Cannot be a workspace dependencies when it's optional
async-recursion = "1.0.2"
notify = "6.1.1"
luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" }
shlex = { version = "1.2.0", optional = true } shlex = { version = "1.2.0", optional = true }
[dev-dependencies] [dev-dependencies]

View file

@ -55,6 +55,7 @@ pub(crate) fn command_definition() -> Command {
) )
} }
/// Try to find a `dtmt.cfg` in the given directory or traverse up the parents.
#[tracing::instrument] #[tracing::instrument]
async fn find_project_config(dir: Option<PathBuf>) -> Result<ModConfig> { async fn find_project_config(dir: Option<PathBuf>) -> Result<ModConfig> {
let (path, mut file) = if let Some(path) = dir { let (path, mut file) = if let Some(path) = dir {
@ -102,39 +103,44 @@ async fn find_project_config(dir: Option<PathBuf>) -> Result<ModConfig> {
Ok(cfg) Ok(cfg)
} }
/// Iterate over the paths in the given `Package` and
/// compile each file by its file type.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn compile_package_files<P>(pkg: &Package, root: P) -> Result<Vec<BundleFile>> async fn compile_package_files(pkg: &Package, cfg: &ModConfig) -> Result<Vec<BundleFile>> {
where let root = Arc::new(&cfg.dir);
P: AsRef<Path> + std::fmt::Debug, let name_overrides = &cfg.name_overrides;
{
let root = Arc::new(root.as_ref());
let tasks = pkg let tasks = pkg
.iter() .iter()
.flat_map(|(file_type, paths)| { .flat_map(|(file_type, names)| {
paths.iter().map(|path| { names.iter().map(|name| {
( (
*file_type, *file_type,
path, name,
// Cloning the `Arc` here solves the issue that in the next `.map`, I need to // Cloning the `Arc` here solves the issue that in the next `.map`, I need to
// `move` the closure parameters, but can't `move` `root` before it was cloned. // `move` the closure parameters, but can't `move` `root` before it was cloned.
root.clone(), root.clone(),
) )
}) })
}) })
.map(|(file_type, path, root)| async move { .map(|(file_type, name, root)| async move {
let sjson = fs::read_to_string(&path).await?; let path = PathBuf::from(name);
let sjson = fs::read_to_string(&path)
.await
.wrap_err_with(|| format!("Failed to read file '{}'", path.display()))?;
let mut path = path.clone(); let name = path.with_extension("").to_slash_lossy().to_string();
path.set_extension(""); let name = if let Some(new_name) = name_overrides.get(&name) {
let new_name = match u64::from_str_radix(new_name, 16) {
BundleFile::from_sjson( Ok(hash) => IdString64::from(hash),
path.to_slash_lossy().to_string(), Err(_) => IdString64::from(new_name.clone()),
file_type, };
sjson, tracing::info!("Overriding '{}' -> '{}'", name, new_name.display());
root.as_ref(), new_name
) } else {
.await IdString64::from(name.clone())
};
BundleFile::from_sjson(name, file_type, sjson, root.as_ref()).await
}); });
let results = futures::stream::iter(tasks) let results = futures::stream::iter(tasks)
@ -145,13 +151,14 @@ where
results.into_iter().collect() results.into_iter().collect()
} }
/// Read a `.package` file, collect the referenced files
/// and compile all of them into a bundle.
#[tracing::instrument] #[tracing::instrument]
async fn build_package<P1, P2>(package: P1, root: P2) -> Result<Bundle> async fn build_package(
where cfg: &ModConfig,
P1: AsRef<Path> + std::fmt::Debug, package: impl AsRef<Path> + std::fmt::Debug,
P2: AsRef<Path> + std::fmt::Debug, ) -> Result<Bundle> {
{ let root = &cfg.dir;
let root = root.as_ref();
let package = package.as_ref(); let package = package.as_ref();
let mut path = root.join(package); let mut path = root.join(package);
@ -165,7 +172,7 @@ where
.await .await
.wrap_err_with(|| format!("Invalid package file {}", &pkg_name))?; .wrap_err_with(|| format!("Invalid package file {}", &pkg_name))?;
let files = compile_package_files(&pkg, root).await?; let files = compile_package_files(&pkg, cfg).await?;
let mut bundle = Bundle::new(pkg_name); let mut bundle = Bundle::new(pkg_name);
for file in files { for file in files {
bundle.add_file(file); bundle.add_file(file);
@ -174,6 +181,8 @@ where
Ok(bundle) Ok(bundle)
} }
/// Cleans the path of internal parent (`../`) or self (`./`) components,
/// and ensures that it is relative.
fn normalize_file_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> { fn normalize_file_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let path = path.as_ref(); let path = path.as_ref();
@ -254,14 +263,14 @@ pub(crate) async fn read_project_config(dir: Option<PathBuf>) -> Result<ModConfi
Ok(cfg) Ok(cfg)
} }
pub(crate) async fn build<P1, P2>( #[tracing::instrument]
pub(crate) async fn build<P>(
cfg: &ModConfig, cfg: &ModConfig,
out_path: P1, out_path: impl AsRef<Path> + std::fmt::Debug,
game_dir: Arc<Option<P2>>, game_dir: Arc<Option<P>>,
) -> Result<()> ) -> Result<()>
where where
P1: AsRef<Path>, P: AsRef<Path> + std::fmt::Debug,
P2: AsRef<Path>,
{ {
let out_path = out_path.as_ref(); let out_path = out_path.as_ref();
@ -286,7 +295,7 @@ where
); );
} }
let bundle = build_package(path, &cfg.dir).await.wrap_err_with(|| { let bundle = build_package(&cfg, path).await.wrap_err_with(|| {
format!( format!(
"Failed to build package '{}' at '{}'", "Failed to build package '{}' at '{}'",
path.display(), path.display(),

View file

@ -0,0 +1,174 @@
use std::{io::Cursor, path::PathBuf};
use clap::{value_parser, Arg, ArgMatches, Command};
use color_eyre::{eyre::Context as _, Result};
use sdk::murmur::{HashGroup, IdString64, Murmur64};
use sdk::{BundleDatabase, FromBinary as _};
use tokio::fs;
pub(crate) fn command_definition() -> Command {
Command::new("db")
.about("Various operations regarding `bundle_database.data`.")
.subcommand_required(true)
.subcommand(
Command::new("list-files")
.about("List bundle contents")
.arg(
Arg::new("database")
.required(true)
.help("Path to the bundle database")
.value_parser(value_parser!(PathBuf)),
)
.arg(
Arg::new("bundle")
.help("The bundle name. If omitted, all bundles will be listed.")
.required(false),
),
)
.subcommand(
Command::new("list-bundles").about("List bundles").arg(
Arg::new("database")
.required(true)
.help("Path to the bundle database")
.value_parser(value_parser!(PathBuf)),
),
)
.subcommand(
Command::new("find-file")
.about("Find the bundle a file belongs to")
.arg(
Arg::new("database")
.required(true)
.help("Path to the bundle database")
.value_parser(value_parser!(PathBuf)),
)
.arg(
Arg::new("file-name")
.required(true)
.help("Name of the file. May be a hash in hex representation or a string"),
),
)
}
#[tracing::instrument(skip_all)]
pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
let Some((op, sub_matches)) = matches.subcommand() else {
unreachable!("clap is configured to require a subcommand");
};
let database = {
let path = sub_matches
.get_one::<PathBuf>("database")
.expect("argument is required");
let binary = fs::read(&path)
.await
.wrap_err_with(|| format!("Failed to read file '{}'", path.display()))?;
let mut r = Cursor::new(binary);
BundleDatabase::from_binary(&mut r).wrap_err("Failed to parse bundle database")?
};
match op {
"list-files" => {
let index = database.files();
if let Some(bundle) = sub_matches.get_one::<String>("bundle") {
let hash = u64::from_str_radix(bundle, 16)
.map(Murmur64::from)
.wrap_err("Invalid hex sequence")?;
if let Some(files) = index.get(&hash) {
for file in files {
let name = ctx.lookup_hash(file.name, HashGroup::Filename);
let extension = file.extension.ext_name();
println!("{}.{}", name.display(), extension);
}
} else {
tracing::info!("Bundle {} not found in the database", bundle);
}
} else {
for (bundle_hash, files) in index.iter() {
let bundle_name = ctx.lookup_hash(*bundle_hash, HashGroup::Filename);
match bundle_name {
IdString64::String(name) => {
println!("{:016x} {}", bundle_hash, name);
}
IdString64::Hash(hash) => {
println!("{:016x}", hash);
}
}
for file in files {
let name = ctx.lookup_hash(file.name, HashGroup::Filename);
let extension = file.extension.ext_name();
match name {
IdString64::String(name) => {
println!("\t{:016x}.{:<12} {}", file.name, extension, name);
}
IdString64::Hash(hash) => {
println!("\t{:016x}.{}", hash, extension);
}
}
}
println!();
}
}
Ok(())
}
"list-bundles" => {
for bundle_hash in database.bundles().keys() {
let bundle_name = ctx.lookup_hash(*bundle_hash, HashGroup::Filename);
match bundle_name {
IdString64::String(name) => {
println!("{:016x} {}", bundle_hash, name);
}
IdString64::Hash(hash) => {
println!("{:016x}", hash);
}
}
}
Ok(())
}
"find-file" => {
let name = sub_matches
.get_one::<String>("file-name")
.expect("required argument");
let name = match u64::from_str_radix(name, 16).map(Murmur64::from) {
Ok(hash) => hash,
Err(_) => Murmur64::hash(name),
};
let bundles = database.files().iter().filter_map(|(bundle_hash, files)| {
if files.iter().any(|file| file.name == name) {
Some(bundle_hash)
} else {
None
}
});
let mut found = false;
for bundle in bundles {
found = true;
println!("{:016x}", bundle);
}
if !found {
std::process::exit(1);
}
Ok(())
}
_ => unreachable!(
"clap is configured to require a subcommand, and they're all handled above"
),
}
}

View file

@ -150,7 +150,7 @@ async fn parse_command_line_template(tmpl: &String) -> Result<CmdLine> {
String::from_utf8_unchecked(bytes.to_vec()) String::from_utf8_unchecked(bytes.to_vec())
}); });
while let Some(arg) = parsed.next() { for arg in parsed.by_ref() {
// Safety: See above. // Safety: See above.
cmd.arg(unsafe { String::from_utf8_unchecked(arg.to_vec()) }); cmd.arg(unsafe { String::from_utf8_unchecked(arg.to_vec()) });
} }

View file

@ -36,6 +36,18 @@ enum OutputFormat {
Text, Text,
} }
fn format_byte_size(size: usize) -> String {
if size < 1024 {
format!("{} Bytes", size)
} else if size < 1024 * 1024 {
format!("{} kB", size / 1024)
} else if size < 1024 * 1024 * 1024 {
format!("{} MB", size / (1024 * 1024))
} else {
format!("{} GB", size / (1024 * 1024 * 1024))
}
}
#[tracing::instrument(skip(ctx))] #[tracing::instrument(skip(ctx))]
async fn print_bundle_contents<P>(ctx: &sdk::Context, path: P, fmt: OutputFormat) -> Result<()> async fn print_bundle_contents<P>(ctx: &sdk::Context, path: P, fmt: OutputFormat) -> Result<()>
where where
@ -50,7 +62,11 @@ where
match fmt { match fmt {
OutputFormat::Text => { OutputFormat::Text => {
println!("Bundle: {}", bundle.name().display()); println!(
"Bundle: {} ({:016x})",
bundle.name().display(),
bundle.name()
);
for f in bundle.files().iter() { for f in bundle.files().iter() {
if f.variants().len() != 1 { if f.variants().len() != 1 {
@ -63,9 +79,10 @@ where
let v = &f.variants()[0]; let v = &f.variants()[0];
println!( println!(
"\t{}.{}: {} bytes", "\t{}.{}: {} ({})",
f.base_name().display(), f.base_name().display(),
f.file_type().ext_name(), f.file_type().ext_name(),
format_byte_size(v.size()),
v.size() v.size()
); );
} }

View file

@ -1,6 +1,7 @@
use clap::{ArgMatches, Command}; use clap::{ArgMatches, Command};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
mod db;
mod decompress; mod decompress;
mod extract; mod extract;
mod inject; mod inject;
@ -14,6 +15,7 @@ pub(crate) fn command_definition() -> Command {
.subcommand(extract::command_definition()) .subcommand(extract::command_definition())
.subcommand(inject::command_definition()) .subcommand(inject::command_definition())
.subcommand(list::command_definition()) .subcommand(list::command_definition())
.subcommand(db::command_definition())
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -23,6 +25,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
Some(("extract", sub_matches)) => extract::run(ctx, sub_matches).await, Some(("extract", sub_matches)) => extract::run(ctx, sub_matches).await,
Some(("inject", sub_matches)) => inject::run(ctx, sub_matches).await, Some(("inject", sub_matches)) => inject::run(ctx, sub_matches).await,
Some(("list", sub_matches)) => list::run(ctx, sub_matches).await, Some(("list", sub_matches)) => list::run(ctx, sub_matches).await,
Some(("db", sub_matches)) => db::run(ctx, sub_matches).await,
_ => unreachable!( _ => unreachable!(
"clap is configured to require a subcommand, and they're all handled above" "clap is configured to require a subcommand, and they're all handled above"
), ),

View file

@ -227,9 +227,12 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
let lookup = &ctx.lookup; let lookup = &ctx.lookup;
let rows: Vec<_> = lookup.entries().iter().map(TableRow::from).collect(); let rows: Vec<_> = lookup.entries().iter().map(TableRow::from).collect();
print_stdout(rows.with_title())?; match print_stdout(rows.with_title()) {
Ok(_) => Ok(()),
Ok(()) // Closing stdout prematurely is normal behavior with things like piping into `head`
Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
Err(err) => Err(err.into()),
}
} }
_ => unreachable!( _ => unreachable!(
"clap is configured to require a subcommand, and they're all handled above" "clap is configured to require a subcommand, and they're all handled above"

View file

@ -351,6 +351,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
}, },
depends: vec![ModDependency::ID(String::from("DMF"))], depends: vec![ModDependency::ID(String::from("DMF"))],
bundled: true, bundled: true,
name_overrides: HashMap::new(),
}; };
tracing::debug!(?dtmt_cfg); tracing::debug!(?dtmt_cfg);

View file

@ -1,18 +1,30 @@
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Arg, ArgMatches, Command}; use clap::{Arg, ArgMatches, Command};
use color_eyre::eyre::{self, Context, Result}; use color_eyre::eyre::{self, Context, Result};
use color_eyre::Help; use color_eyre::Help;
use futures::{StreamExt, TryStreamExt}; use futures::{StreamExt, TryStreamExt};
use string_template::Template; use minijinja::Environment;
use tokio::fs::{self, DirBuilder}; use tokio::fs::{self, DirBuilder};
const TEMPLATES: [(&str, &str); 5] = [ const TEMPLATES: [(&str, &str); 5] = [
( (
"dtmt.cfg", "dtmt.cfg",
r#"id = "{{id}}" r#"//
// This is your mod's main configuration file. It tells DTMT how to build the mod,
// and DTMM what to display to your users.
// Certain files have been pre-filled by the template, the ones commented out (`//`)
// are optional.
//
// A unique identifier (preferably lower case, alphanumeric)
id = "{{id}}"
// The display name that your users will see.
// This doesn't have to be unique, but you still want to avoid being confused with other
// mods.
name = "{{name}}" name = "{{name}}"
// It's good practice to increase this number whenever you publish changes.
// It's up to you if you use SemVer or something simpler like `1970-12-24`. It should sort and
// compare well, though.
version = "0.1.0" version = "0.1.0"
// author = "" // author = ""
@ -32,16 +44,25 @@ categories = [
// A list of mod IDs that this mod depends on. You can find // A list of mod IDs that this mod depends on. You can find
// those IDs by downloading the mod and extracting their `dtmt.cfg`. // those IDs by downloading the mod and extracting their `dtmt.cfg`.
// To make your fellow modders' lives easier, publish your own mods' IDs
// somewhere visible, such as the Nexusmods page.
depends = [ depends = [
DMF DMF
] ]
// The primary resources that serve as the entry point to your
// mod's code. Unless for very specific use cases, the generated
// values shouldn't be changed.
resources = { resources = {
init = "scripts/mods/{{id}}/init" init = "scripts/mods/{{id}}/init"
data = "scripts/mods/{{id}}/data" data = "scripts/mods/{{id}}/data"
localization = "scripts/mods/{{id}}/localization" localization = "scripts/mods/{{id}}/localization"
} }
// The list of packages, or bundles, to build.
// Each one corresponds to a package definition in the named folder.
// For mods that contain only code and/or a few small assets, a single
// package will suffice.
packages = [ packages = [
"packages/mods/{{id}}" "packages/mods/{{id}}"
] ]
@ -59,7 +80,6 @@ packages = [
r#"local mod = get_mod("{{id}}") r#"local mod = get_mod("{{id}}")
-- Your mod code goes here. -- Your mod code goes here.
-- https://vmf-docs.verminti.de
"#, "#,
), ),
( (
@ -137,34 +157,45 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
tracing::debug!(root = %root.display(), name, id); tracing::debug!(root = %root.display(), name, id);
let mut data = HashMap::new(); let render_ctx = minijinja::context!(name => name.as_str(), id => id.as_str());
data.insert("name", name.as_str()); let env = Environment::new();
data.insert("id", id.as_str());
let templates = TEMPLATES let templates = TEMPLATES
.iter() .iter()
.map(|(path_tmpl, content_tmpl)| { .map(|(path_tmpl, content_tmpl)| {
let path = Template::new(path_tmpl).render(&data); env.render_str(path_tmpl, &render_ctx)
let content = Template::new(content_tmpl).render(&data); .wrap_err_with(|| format!("Failed to render template: {}", path_tmpl))
.and_then(|path| {
(root.join(path), content) env.render_named_str(&path, content_tmpl, &render_ctx)
.wrap_err_with(|| format!("Failed to render template '{}'", &path))
.map(|content| (root.join(path), content))
})
}) })
.map(|(path, content)| async move { .map(|res| async move {
let dir = path match res {
.parent() Ok((path, content)) => {
.ok_or_else(|| eyre::eyre!("invalid root path"))?; let dir = path
.parent()
.ok_or_else(|| eyre::eyre!("invalid root path"))?;
DirBuilder::new() DirBuilder::new()
.recursive(true) .recursive(true)
.create(&dir) .create(&dir)
.await .await
.wrap_err_with(|| format!("Failed to create directory {}", dir.display()))?; .wrap_err_with(|| {
format!("Failed to create directory {}", dir.display())
})?;
tracing::trace!("Writing file {}", path.display()); tracing::trace!("Writing file {}", path.display());
fs::write(&path, content.as_bytes()) fs::write(&path, content.as_bytes())
.await .await
.wrap_err_with(|| format!("Failed to write content to path {}", path.display())) .wrap_err_with(|| {
format!("Failed to write content to path {}", path.display())
})
}
Err(e) => Err(e),
}
}); });
futures::stream::iter(templates) futures::stream::iter(templates)

View file

@ -77,17 +77,14 @@ pub(crate) fn command_definition() -> Command {
) )
} }
async fn compile<P1, P2, P3>( #[tracing::instrument]
async fn compile(
cfg: &ModConfig, cfg: &ModConfig,
out_path: P1, out_path: impl AsRef<Path> + std::fmt::Debug,
archive_path: P2, archive_path: impl AsRef<Path> + std::fmt::Debug,
game_dir: Arc<Option<P3>>, game_dir: Arc<Option<impl AsRef<Path> + std::fmt::Debug>>,
) -> Result<()> ) -> Result<()> {
where let out_path = out_path.as_ref();
P1: AsRef<Path> + std::marker::Copy,
P2: AsRef<Path>,
P3: AsRef<Path>,
{
build(cfg, out_path, game_dir) build(cfg, out_path, game_dir)
.await .await
.wrap_err("Failed to build bundles")?; .wrap_err("Failed to build bundles")?;

View file

@ -54,17 +54,11 @@ impl<'a> ShellParser<'a> {
} }
_ => {} _ => {}
}, },
ParserState::SingleQuote => match c { ParserState::SingleQuote => if c == b'\'' {
b'\'' => { return Some(&self.bytes[start..(self.offset - 1)]);
return Some(&self.bytes[start..(self.offset - 1)]);
}
_ => {}
}, },
ParserState::DoubleQuote => match c { ParserState::DoubleQuote => if c == b'"' {
b'"' => { return Some(&self.bytes[start..(self.offset - 1)]);
return Some(&self.bytes[start..(self.offset - 1)]);
}
_ => {}
}, },
} }
} }

@ -1 +1 @@
Subproject commit b40962a61c748756d7da293d9fff26aca019603e Subproject commit 228b8ca37ee79ab9afa45c40da415e4dcb029751

View file

@ -6,11 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ansi_term = "0.12.1" ansi_term = { workspace = true }
color-eyre = "0.6.2" color-eyre = { workspace = true }
serde = "1.0.152" serde = { workspace = true }
steamlocate = "2.0.0-beta.2" steamlocate = { workspace = true }
time = { version = "0.3.19", features = ["formatting", "local-offset", "macros"] } time = { workspace = true }
tracing = "0.1.37" tracing = { workspace = true }
tracing-error = "0.2.0" tracing-error = { workspace = true }
tracing-subscriber = "0.3.16" tracing-subscriber = { workspace = true }

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use color_eyre::eyre::{OptionExt as _, WrapErr as _}; use color_eyre::eyre::{OptionExt as _, WrapErr as _};
@ -67,6 +68,8 @@ pub struct ModConfig {
pub depends: Vec<ModDependency>, pub depends: Vec<ModDependency>,
#[serde(default = "default_true", skip_serializing_if = "is_true")] #[serde(default = "default_true", skip_serializing_if = "is_true")]
pub bundled: bool, pub bundled: bool,
#[serde(default)]
pub name_overrides: HashMap<String, String>,
} }
pub const STEAMAPP_ID: u32 = 1361210; pub const STEAMAPP_ID: u32 = 1361210;
@ -84,7 +87,7 @@ pub fn collect_game_info() -> Result<Option<GameInfo>> {
.find_app(STEAMAPP_ID) .find_app(STEAMAPP_ID)
.wrap_err("Failed to look up game by Steam app ID")?; .wrap_err("Failed to look up game by Steam app ID")?;
let Some((app, _)) = found else { let Some((app, library)) = found else {
return Ok(None); return Ok(None);
}; };
@ -93,7 +96,7 @@ pub fn collect_game_info() -> Result<Option<GameInfo>> {
.ok_or_eyre("Missing field 'last_updated'")?; .ok_or_eyre("Missing field 'last_updated'")?;
Ok(Some(GameInfo { Ok(Some(GameInfo {
path: app.install_dir.into(), path: library.path().join(app.install_dir),
last_updated: last_updated.into(), last_updated: last_updated.into(),
})) }))
} }

View file

@ -84,7 +84,7 @@ pub fn create_tracing_subscriber() {
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::try_new("info").unwrap()); EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::try_new("info").unwrap());
let (dev_stdout_layer, prod_stdout_layer, filter_layer) = if cfg!(debug_assertions) { let (dev_stdout_layer, prod_stdout_layer, filter_layer) = if cfg!(debug_assertions) {
let fmt_layer = fmt::layer().pretty(); let fmt_layer = fmt::layer().pretty().with_writer(std::io::stderr);
(Some(fmt_layer), None, None) (Some(fmt_layer), None, None)
} else { } else {
// Creates a layer that // Creates a layer that
@ -93,6 +93,7 @@ pub fn create_tracing_subscriber() {
// - does not print spans/targets // - does not print spans/targets
// - only prints time, not date // - only prints time, not date
let fmt_layer = fmt::layer() let fmt_layer = fmt::layer()
.with_writer(std::io::stderr)
.event_format(Formatter) .event_format(Formatter)
.fmt_fields(debug_fn(format_fields)); .fmt_fields(debug_fn(format_fields));

@ -1 +1 @@
Subproject commit 5d1a075742395f767c79d9c0d7466c6fb442f106 Subproject commit 6d94a4dd2c296bf1f044ee4c70fb10dca4c1c241

View file

@ -12,7 +12,7 @@ regex = "1.7.1"
reqwest = { version = "0.12.4" } reqwest = { version = "0.12.4" }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.94" serde_json = "1.0.94"
thiserror = "1.0.39" thiserror = "2.0.0"
time = { version = "0.3.20", features = ["serde"] } time = { version = "0.3.20", features = ["serde"] }
tracing = "0.1.37" tracing = "0.1.37"
url = { version = "2.3.1", features = ["serde"] } url = { version = "2.3.1", features = ["serde"] }

View file

@ -28,7 +28,7 @@ pub enum Error {
HTTP(#[from] reqwest::Error), HTTP(#[from] reqwest::Error),
#[error("invalid URL: {0:?}")] #[error("invalid URL: {0:?}")]
URLParseError(#[from] url::ParseError), URLParseError(#[from] url::ParseError),
#[error("failed to deserialize '{error}': {json}")] #[error("failed to deserialize due to {error}: {json}")]
Deserialize { Deserialize {
json: String, json: String,
error: serde_json::Error, error: serde_json::Error,
@ -37,7 +37,7 @@ pub enum Error {
InvalidHeaderValue(#[from] InvalidHeaderValue), InvalidHeaderValue(#[from] InvalidHeaderValue),
#[error("this error cannot happen")] #[error("this error cannot happen")]
Infallible(#[from] Infallible), Infallible(#[from] Infallible),
#[error("invalid NXM URL '{}': {0}", .1.as_str())] #[error("invalid NXM URL '{url}': {0}", url = .1.as_str())]
InvalidNXM(&'static str, Url), InvalidNXM(&'static str, Url),
#[error("{0}")] #[error("{0}")]
Custom(String), Custom(String),

View file

@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
color-eyre = "0.6.2" color-eyre = { workspace = true }
tracing = "0.1.37" tracing = { workspace = true }
[build-dependencies] [build-dependencies]
bindgen = "0.69.4" bindgen = "0.71.0"

View file

@ -7,6 +7,7 @@ use std::ptr;
use color_eyre::{eyre, Result}; use color_eyre::{eyre, Result};
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::identity_op)]
mod bindings { mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs")); include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
} }

View file

@ -4,23 +4,23 @@ version = "0.3.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
bitflags = "2.5.0" async-recursion = { workspace = true }
byteorder = "1.4.3" bitflags = { workspace = true }
color-eyre = "0.6.2" byteorder = { workspace = true }
csv-async = { version = "1.2.4", features = ["tokio", "serde"] } color-eyre = { workspace = true }
fastrand = "2.1.0" csv-async = { workspace = true }
futures = "0.3.25" fastrand = { workspace = true }
futures-util = "0.3.24" futures = { workspace = true }
glob = "0.3.0" futures-util = { workspace = true }
nanorand = "0.7.0" glob = { workspace = true }
pin-project-lite = "0.2.9" luajit2-sys = { workspace = true }
serde = { version = "1.0.147", features = ["derive"] } nanorand = { workspace = true }
serde_sjson = { path = "../../lib/serde_sjson", version = "*" } oodle = { workspace = true }
oodle = { path = "../../lib/oodle", version = "*" } path-slash = { workspace = true }
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] } pin-project-lite = { workspace = true }
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] } serde = { workspace = true }
tracing = { version = "0.1.37", features = ["async-await"] } serde_sjson = { workspace = true }
tracing-error = "0.2.0" tokio = { workspace = true }
luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" } tokio-stream = { workspace = true }
async-recursion = "1.0.2" tracing = { workspace = true }
path-slash = "0.2.1" tracing-error = { workspace = true }

View file

@ -43,6 +43,7 @@ impl<T: FromBinary> FromBinary for Vec<T> {
} }
pub mod sync { pub mod sync {
use std::ffi::CStr;
use std::io::{self, Read, Seek, SeekFrom}; use std::io::{self, Read, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
@ -165,25 +166,13 @@ pub mod sync {
} }
fn read_string_len(&mut self, len: usize) -> Result<String> { fn read_string_len(&mut self, len: usize) -> Result<String> {
let mut buf = vec![0; len]; let pos = self.stream_position();
let res = self
.read_exact(&mut buf)
.map_err(Report::new)
.and_then(|_| {
String::from_utf8(buf).map_err(|err| {
let ascii = String::from_utf8_lossy(err.as_bytes()).to_string();
let bytes = format!("{:?}", err.as_bytes());
Report::new(err)
.with_section(move || bytes.header("Bytes:"))
.with_section(move || ascii.header("ASCII:"))
})
});
let res = read_string_len(self, len);
if res.is_ok() { if res.is_ok() {
return res; return res;
} }
let pos = self.stream_position();
if pos.is_ok() { if pos.is_ok() {
res.with_section(|| { res.with_section(|| {
format!("{pos:#X} ({pos})", pos = pos.unwrap()).header("Position: ") format!("{pos:#X} ({pos})", pos = pos.unwrap()).header("Position: ")
@ -243,4 +232,22 @@ pub mod sync {
Err(err).with_section(|| format!("{pos:#X} ({pos})").header("Position: ")) Err(err).with_section(|| format!("{pos:#X} ({pos})").header("Position: "))
} }
fn read_string_len(mut r: impl Read, len: usize) -> Result<String> {
let mut buf = vec![0; len];
r.read_exact(&mut buf)
.wrap_err_with(|| format!("Failed to read {} bytes", len))?;
let res = match CStr::from_bytes_until_nul(&buf) {
Ok(s) => {
let s = s.to_str()?;
Ok(s.to_string())
}
Err(_) => String::from_utf8(buf.clone()).map_err(Report::new),
};
res.wrap_err("Invalid binary for UTF8 string")
.with_section(|| format!("{}", String::from_utf8_lossy(&buf)).header("ASCI:"))
.with_section(|| format!("{:x?}", buf).header("Bytes:"))
}
} }

View file

@ -13,21 +13,21 @@ use crate::binary::ToBinary;
use crate::murmur::Murmur64; use crate::murmur::Murmur64;
use crate::Bundle; use crate::Bundle;
use super::file::BundleFileType; use super::filetype::BundleFileType;
const DATABASE_VERSION: u32 = 0x6; const DATABASE_VERSION: u32 = 0x6;
const FILE_VERSION: u32 = 0x4; const FILE_VERSION: u32 = 0x4;
pub struct BundleFile { pub struct BundleFile {
name: String, pub name: String,
stream: String, pub stream: String,
platform_specific: bool, pub platform_specific: bool,
file_time: u64, pub file_time: u64,
} }
pub struct FileName { pub struct FileName {
extension: BundleFileType, pub extension: BundleFileType,
name: Murmur64, pub name: Murmur64,
} }
pub struct BundleDatabase { pub struct BundleDatabase {
@ -36,7 +36,34 @@ pub struct BundleDatabase {
bundle_contents: HashMap<Murmur64, Vec<FileName>>, bundle_contents: HashMap<Murmur64, Vec<FileName>>,
} }
// Implements the partial Murmur that's used by the engine to compute bundle resource hashes,
// but in a way that the loop can be done outside the function.
#[inline(always)]
fn add_to_resource_hash(mut k: u64, name: impl Into<u64>) -> u64 {
const M: u64 = 0xc6a4a7935bd1e995;
const R: u64 = 47;
let mut h: u64 = name.into();
k = k.wrapping_mul(M);
k ^= k >> R;
k = k.wrapping_mul(M);
h ^= k;
k = M.wrapping_mul(h);
k
}
impl BundleDatabase { impl BundleDatabase {
pub fn bundles(&self) -> &HashMap<Murmur64, Vec<BundleFile>> {
&self.stored_files
}
pub fn files(&self) -> &HashMap<Murmur64, Vec<FileName>> {
&self.bundle_contents
}
pub fn add_bundle(&mut self, bundle: &Bundle) { pub fn add_bundle(&mut self, bundle: &Bundle) {
let hash = bundle.name().to_murmur64(); let hash = bundle.name().to_murmur64();
let name = hash.to_string(); let name = hash.to_string();
@ -69,20 +96,26 @@ impl BundleDatabase {
} }
} }
let mut resource_hash = 0;
for f in bundle.files() { for f in bundle.files() {
let name = f.base_name().to_murmur64();
let file_name = FileName { let file_name = FileName {
extension: f.file_type(), extension: f.file_type(),
name: f.base_name().to_murmur64(), name,
}; };
// TODO: Compute actual resource hash resource_hash = add_to_resource_hash(resource_hash, name);
self.resource_hashes.insert(hash, 0);
// TODO: Make sure each file name only exists once. Probably best to turn
// the `Vec` into a sorted `HashSet`.
self.bundle_contents self.bundle_contents
.entry(hash) .entry(hash)
.or_default() .or_default()
.push(file_name); .push(file_name);
} }
self.resource_hashes.insert(hash, resource_hash);
} }
} }
@ -103,7 +136,7 @@ impl FromBinary for BundleDatabase {
let mut stored_files = HashMap::with_capacity(num_entries); let mut stored_files = HashMap::with_capacity(num_entries);
for _ in 0..num_entries { for _ in 0..num_entries {
let hash = Murmur64::from(r.read_u64()?); let hash = r.read_u64().map(Murmur64::from)?;
let num_files = r.read_u32()? as usize; let num_files = r.read_u32()? as usize;
let mut files = Vec::with_capacity(num_files); let mut files = Vec::with_capacity(num_files);
@ -161,7 +194,7 @@ impl FromBinary for BundleDatabase {
let mut resource_hashes = HashMap::with_capacity(num_hashes); let mut resource_hashes = HashMap::with_capacity(num_hashes);
for _ in 0..num_hashes { for _ in 0..num_hashes {
let name = Murmur64::from(r.read_u64()?); let name = r.read_u64().map(Murmur64::from)?;
let hash = r.read_u64()?; let hash = r.read_u64()?;
resource_hashes.insert(name, hash); resource_hashes.insert(name, hash);
@ -171,14 +204,14 @@ impl FromBinary for BundleDatabase {
let mut bundle_contents = HashMap::with_capacity(num_contents); let mut bundle_contents = HashMap::with_capacity(num_contents);
for _ in 0..num_contents { for _ in 0..num_contents {
let hash = Murmur64::from(r.read_u64()?); let hash = r.read_u64().map(Murmur64::from)?;
let num_files = r.read_u32()? as usize; let num_files = r.read_u32()? as usize;
let mut files = Vec::with_capacity(num_files); let mut files = Vec::with_capacity(num_files);
for _ in 0..num_files { for _ in 0..num_files {
let extension = BundleFileType::from(r.read_u64()?); let extension = r.read_u64().map(BundleFileType::from)?;
let name = Murmur64::from(r.read_u64()?); let name = r.read_u64().map(Murmur64::from)?;
files.push(FileName { extension, name }); files.push(FileName { extension, name });
} }

View file

@ -5,407 +5,12 @@ use bitflags::bitflags;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::{eyre, Result}; use color_eyre::{eyre, Result};
use futures::future::join_all; use futures::future::join_all;
use serde::Serialize;
use crate::binary::sync::*; use crate::binary::sync::*;
use crate::filetype::*; use crate::filetype::*;
use crate::murmur::{HashGroup, IdString64, Murmur64}; use crate::murmur::{HashGroup, IdString64, Murmur64};
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] use super::filetype::BundleFileType;
pub enum BundleFileType {
Animation,
AnimationCurves,
Apb,
BakedLighting,
Bik,
BlendSet,
Bones,
Chroma,
CommonPackage,
Config,
Crypto,
Data,
Entity,
Flow,
Font,
Ies,
Ini,
Input,
Ivf,
Keys,
Level,
Lua,
Material,
Mod,
MouseCursor,
NavData,
NetworkConfig,
OddleNet,
Package,
Particles,
PhysicsProperties,
RenderConfig,
RtPipeline,
Scene,
Shader,
ShaderLibrary,
ShaderLibraryGroup,
ShadingEnvionmentMapping,
ShadingEnvironment,
Slug,
SlugAlbum,
SoundEnvironment,
SpuJob,
StateMachine,
StaticPVS,
Strings,
SurfaceProperties,
Texture,
TimpaniBank,
TimpaniMaster,
Tome,
Ugg,
Unit,
Upb,
VectorField,
Wav,
WwiseBank,
WwiseDep,
WwiseEvent,
WwiseMetadata,
WwiseStream,
Xml,
Unknown(Murmur64),
}
impl BundleFileType {
pub fn ext_name(&self) -> String {
match self {
BundleFileType::AnimationCurves => String::from("animation_curves"),
BundleFileType::Animation => String::from("animation"),
BundleFileType::Apb => String::from("apb"),
BundleFileType::BakedLighting => String::from("baked_lighting"),
BundleFileType::Bik => String::from("bik"),
BundleFileType::BlendSet => String::from("blend_set"),
BundleFileType::Bones => String::from("bones"),
BundleFileType::Chroma => String::from("chroma"),
BundleFileType::CommonPackage => String::from("common_package"),
BundleFileType::Config => String::from("config"),
BundleFileType::Crypto => String::from("crypto"),
BundleFileType::Data => String::from("data"),
BundleFileType::Entity => String::from("entity"),
BundleFileType::Flow => String::from("flow"),
BundleFileType::Font => String::from("font"),
BundleFileType::Ies => String::from("ies"),
BundleFileType::Ini => String::from("ini"),
BundleFileType::Input => String::from("input"),
BundleFileType::Ivf => String::from("ivf"),
BundleFileType::Keys => String::from("keys"),
BundleFileType::Level => String::from("level"),
BundleFileType::Lua => String::from("lua"),
BundleFileType::Material => String::from("material"),
BundleFileType::Mod => String::from("mod"),
BundleFileType::MouseCursor => String::from("mouse_cursor"),
BundleFileType::NavData => String::from("nav_data"),
BundleFileType::NetworkConfig => String::from("network_config"),
BundleFileType::OddleNet => String::from("oodle_net"),
BundleFileType::Package => String::from("package"),
BundleFileType::Particles => String::from("particles"),
BundleFileType::PhysicsProperties => String::from("physics_properties"),
BundleFileType::RenderConfig => String::from("render_config"),
BundleFileType::RtPipeline => String::from("rt_pipeline"),
BundleFileType::Scene => String::from("scene"),
BundleFileType::ShaderLibraryGroup => String::from("shader_library_group"),
BundleFileType::ShaderLibrary => String::from("shader_library"),
BundleFileType::Shader => String::from("shader"),
BundleFileType::ShadingEnvionmentMapping => String::from("shading_environment_mapping"),
BundleFileType::ShadingEnvironment => String::from("shading_environment"),
BundleFileType::SlugAlbum => String::from("slug_album"),
BundleFileType::Slug => String::from("slug"),
BundleFileType::SoundEnvironment => String::from("sound_environment"),
BundleFileType::SpuJob => String::from("spu_job"),
BundleFileType::StateMachine => String::from("state_machine"),
BundleFileType::StaticPVS => String::from("static_pvs"),
BundleFileType::Strings => String::from("strings"),
BundleFileType::SurfaceProperties => String::from("surface_properties"),
BundleFileType::Texture => String::from("texture"),
BundleFileType::TimpaniBank => String::from("timpani_bank"),
BundleFileType::TimpaniMaster => String::from("timpani_master"),
BundleFileType::Tome => String::from("tome"),
BundleFileType::Ugg => String::from("ugg"),
BundleFileType::Unit => String::from("unit"),
BundleFileType::Upb => String::from("upb"),
BundleFileType::VectorField => String::from("vector_field"),
BundleFileType::Wav => String::from("wav"),
BundleFileType::WwiseBank => String::from("wwise_bank"),
BundleFileType::WwiseDep => String::from("wwise_dep"),
BundleFileType::WwiseEvent => String::from("wwise_event"),
BundleFileType::WwiseMetadata => String::from("wwise_metadata"),
BundleFileType::WwiseStream => String::from("wwise_stream"),
BundleFileType::Xml => String::from("xml"),
BundleFileType::Unknown(s) => format!("{s:016X}"),
}
}
pub fn decompiled_ext_name(&self) -> String {
match self {
BundleFileType::Texture => String::from("dds"),
BundleFileType::WwiseBank => String::from("bnk"),
BundleFileType::WwiseStream => String::from("ogg"),
_ => self.ext_name(),
}
}
pub fn hash(&self) -> Murmur64 {
Murmur64::from(*self)
}
}
impl std::str::FromStr for BundleFileType {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let val = match s {
"animation_curves" => BundleFileType::AnimationCurves,
"animation" => BundleFileType::Animation,
"apb" => BundleFileType::Apb,
"baked_lighting" => BundleFileType::BakedLighting,
"bik" => BundleFileType::Bik,
"blend_set" => BundleFileType::BlendSet,
"bones" => BundleFileType::Bones,
"chroma" => BundleFileType::Chroma,
"common_package" => BundleFileType::CommonPackage,
"config" => BundleFileType::Config,
"crypto" => BundleFileType::Crypto,
"data" => BundleFileType::Data,
"entity" => BundleFileType::Entity,
"flow" => BundleFileType::Flow,
"font" => BundleFileType::Font,
"ies" => BundleFileType::Ies,
"ini" => BundleFileType::Ini,
"input" => BundleFileType::Input,
"ivf" => BundleFileType::Ivf,
"keys" => BundleFileType::Keys,
"level" => BundleFileType::Level,
"lua" => BundleFileType::Lua,
"material" => BundleFileType::Material,
"mod" => BundleFileType::Mod,
"mouse_cursor" => BundleFileType::MouseCursor,
"nav_data" => BundleFileType::NavData,
"network_config" => BundleFileType::NetworkConfig,
"oodle_net" => BundleFileType::OddleNet,
"package" => BundleFileType::Package,
"particles" => BundleFileType::Particles,
"physics_properties" => BundleFileType::PhysicsProperties,
"render_config" => BundleFileType::RenderConfig,
"rt_pipeline" => BundleFileType::RtPipeline,
"scene" => BundleFileType::Scene,
"shader_library_group" => BundleFileType::ShaderLibraryGroup,
"shader_library" => BundleFileType::ShaderLibrary,
"shader" => BundleFileType::Shader,
"shading_environment_mapping" => BundleFileType::ShadingEnvionmentMapping,
"shading_environment" => BundleFileType::ShadingEnvironment,
"slug_album" => BundleFileType::SlugAlbum,
"slug" => BundleFileType::Slug,
"sound_environment" => BundleFileType::SoundEnvironment,
"spu_job" => BundleFileType::SpuJob,
"state_machine" => BundleFileType::StateMachine,
"static_pvs" => BundleFileType::StaticPVS,
"strings" => BundleFileType::Strings,
"surface_properties" => BundleFileType::SurfaceProperties,
"texture" => BundleFileType::Texture,
"timpani_bank" => BundleFileType::TimpaniBank,
"timpani_master" => BundleFileType::TimpaniMaster,
"tome" => BundleFileType::Tome,
"ugg" => BundleFileType::Ugg,
"unit" => BundleFileType::Unit,
"upb" => BundleFileType::Upb,
"vector_field" => BundleFileType::VectorField,
"wav" => BundleFileType::Wav,
"wwise_bank" => BundleFileType::WwiseBank,
"wwise_dep" => BundleFileType::WwiseDep,
"wwise_event" => BundleFileType::WwiseEvent,
"wwise_metadata" => BundleFileType::WwiseMetadata,
"wwise_stream" => BundleFileType::WwiseStream,
"xml" => BundleFileType::Xml,
s => eyre::bail!("Unknown type string '{}'", s),
};
Ok(val)
}
}
impl Serialize for BundleFileType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let value = self.ext_name();
value.serialize(serializer)
}
}
impl From<Murmur64> for BundleFileType {
fn from(value: Murmur64) -> Self {
Self::from(Into::<u64>::into(value))
}
}
impl From<u64> for BundleFileType {
fn from(hash: u64) -> BundleFileType {
match hash {
0x931e336d7646cc26 => BundleFileType::Animation,
0xdcfb9e18fff13984 => BundleFileType::AnimationCurves,
0x3eed05ba83af5090 => BundleFileType::Apb,
0x7ffdb779b04e4ed1 => BundleFileType::BakedLighting,
0xaa5965f03029fa18 => BundleFileType::Bik,
0xe301e8af94e3b5a3 => BundleFileType::BlendSet,
0x18dead01056b72e9 => BundleFileType::Bones,
0xb7893adf7567506a => BundleFileType::Chroma,
0xfe9754bd19814a47 => BundleFileType::CommonPackage,
0x82645835e6b73232 => BundleFileType::Config,
0x69108ded1e3e634b => BundleFileType::Crypto,
0x8fd0d44d20650b68 => BundleFileType::Data,
0x9831ca893b0d087d => BundleFileType::Entity,
0x92d3ee038eeb610d => BundleFileType::Flow,
0x9efe0a916aae7880 => BundleFileType::Font,
0x8f7d5a2c0f967655 => BundleFileType::Ies,
0xd526a27da14f1dc5 => BundleFileType::Ini,
0x2bbcabe5074ade9e => BundleFileType::Input,
0xfa4a8e091a91201e => BundleFileType::Ivf,
0xa62f9297dc969e85 => BundleFileType::Keys,
0x2a690fd348fe9ac5 => BundleFileType::Level,
0xa14e8dfa2cd117e2 => BundleFileType::Lua,
0xeac0b497876adedf => BundleFileType::Material,
0x3fcdd69156a46417 => BundleFileType::Mod,
0xb277b11fe4a61d37 => BundleFileType::MouseCursor,
0x169de9566953d264 => BundleFileType::NavData,
0x3b1fa9e8f6bac374 => BundleFileType::NetworkConfig,
0xb0f2c12eb107f4d8 => BundleFileType::OddleNet,
0xad9c6d9ed1e5e77a => BundleFileType::Package,
0xa8193123526fad64 => BundleFileType::Particles,
0xbf21403a3ab0bbb1 => BundleFileType::PhysicsProperties,
0x27862fe24795319c => BundleFileType::RenderConfig,
0x9ca183c2d0e76dee => BundleFileType::RtPipeline,
0x9d0a795bfe818d19 => BundleFileType::Scene,
0xcce8d5b5f5ae333f => BundleFileType::Shader,
0xe5ee32a477239a93 => BundleFileType::ShaderLibrary,
0x9e5c3cc74575aeb5 => BundleFileType::ShaderLibraryGroup,
0x250e0a11ac8e26f8 => BundleFileType::ShadingEnvionmentMapping,
0xfe73c7dcff8a7ca5 => BundleFileType::ShadingEnvironment,
0xa27b4d04a9ba6f9e => BundleFileType::Slug,
0xe9fc9ea7042e5ec0 => BundleFileType::SlugAlbum,
0xd8b27864a97ffdd7 => BundleFileType::SoundEnvironment,
0xf97af9983c05b950 => BundleFileType::SpuJob,
0xa486d4045106165c => BundleFileType::StateMachine,
0xe3f0baa17d620321 => BundleFileType::StaticPVS,
0x0d972bab10b40fd3 => BundleFileType::Strings,
0xad2d3fa30d9ab394 => BundleFileType::SurfaceProperties,
0xcd4238c6a0c69e32 => BundleFileType::Texture,
0x99736be1fff739a4 => BundleFileType::TimpaniBank,
0x00a3e6c59a2b9c6c => BundleFileType::TimpaniMaster,
0x19c792357c99f49b => BundleFileType::Tome,
0x712d6e3dd1024c9c => BundleFileType::Ugg,
0xe0a48d0be9a7453f => BundleFileType::Unit,
0xa99510c6e86dd3c2 => BundleFileType::Upb,
0xf7505933166d6755 => BundleFileType::VectorField,
0x786f65c00a816b19 => BundleFileType::Wav,
0x535a7bd3e650d799 => BundleFileType::WwiseBank,
0xaf32095c82f2b070 => BundleFileType::WwiseDep,
0xaabdd317b58dfc8a => BundleFileType::WwiseEvent,
0xd50a8b7e1c82b110 => BundleFileType::WwiseMetadata,
0x504b55235d21440e => BundleFileType::WwiseStream,
0x76015845a6003765 => BundleFileType::Xml,
_ => BundleFileType::Unknown(Murmur64::from(hash)),
}
}
}
impl From<BundleFileType> for u64 {
fn from(t: BundleFileType) -> u64 {
match t {
BundleFileType::Animation => 0x931e336d7646cc26,
BundleFileType::AnimationCurves => 0xdcfb9e18fff13984,
BundleFileType::Apb => 0x3eed05ba83af5090,
BundleFileType::BakedLighting => 0x7ffdb779b04e4ed1,
BundleFileType::Bik => 0xaa5965f03029fa18,
BundleFileType::BlendSet => 0xe301e8af94e3b5a3,
BundleFileType::Bones => 0x18dead01056b72e9,
BundleFileType::Chroma => 0xb7893adf7567506a,
BundleFileType::CommonPackage => 0xfe9754bd19814a47,
BundleFileType::Config => 0x82645835e6b73232,
BundleFileType::Crypto => 0x69108ded1e3e634b,
BundleFileType::Data => 0x8fd0d44d20650b68,
BundleFileType::Entity => 0x9831ca893b0d087d,
BundleFileType::Flow => 0x92d3ee038eeb610d,
BundleFileType::Font => 0x9efe0a916aae7880,
BundleFileType::Ies => 0x8f7d5a2c0f967655,
BundleFileType::Ini => 0xd526a27da14f1dc5,
BundleFileType::Input => 0x2bbcabe5074ade9e,
BundleFileType::Ivf => 0xfa4a8e091a91201e,
BundleFileType::Keys => 0xa62f9297dc969e85,
BundleFileType::Level => 0x2a690fd348fe9ac5,
BundleFileType::Lua => 0xa14e8dfa2cd117e2,
BundleFileType::Material => 0xeac0b497876adedf,
BundleFileType::Mod => 0x3fcdd69156a46417,
BundleFileType::MouseCursor => 0xb277b11fe4a61d37,
BundleFileType::NavData => 0x169de9566953d264,
BundleFileType::NetworkConfig => 0x3b1fa9e8f6bac374,
BundleFileType::OddleNet => 0xb0f2c12eb107f4d8,
BundleFileType::Package => 0xad9c6d9ed1e5e77a,
BundleFileType::Particles => 0xa8193123526fad64,
BundleFileType::PhysicsProperties => 0xbf21403a3ab0bbb1,
BundleFileType::RenderConfig => 0x27862fe24795319c,
BundleFileType::RtPipeline => 0x9ca183c2d0e76dee,
BundleFileType::Scene => 0x9d0a795bfe818d19,
BundleFileType::Shader => 0xcce8d5b5f5ae333f,
BundleFileType::ShaderLibrary => 0xe5ee32a477239a93,
BundleFileType::ShaderLibraryGroup => 0x9e5c3cc74575aeb5,
BundleFileType::ShadingEnvionmentMapping => 0x250e0a11ac8e26f8,
BundleFileType::ShadingEnvironment => 0xfe73c7dcff8a7ca5,
BundleFileType::Slug => 0xa27b4d04a9ba6f9e,
BundleFileType::SlugAlbum => 0xe9fc9ea7042e5ec0,
BundleFileType::SoundEnvironment => 0xd8b27864a97ffdd7,
BundleFileType::SpuJob => 0xf97af9983c05b950,
BundleFileType::StateMachine => 0xa486d4045106165c,
BundleFileType::StaticPVS => 0xe3f0baa17d620321,
BundleFileType::Strings => 0x0d972bab10b40fd3,
BundleFileType::SurfaceProperties => 0xad2d3fa30d9ab394,
BundleFileType::Texture => 0xcd4238c6a0c69e32,
BundleFileType::TimpaniBank => 0x99736be1fff739a4,
BundleFileType::TimpaniMaster => 0x00a3e6c59a2b9c6c,
BundleFileType::Tome => 0x19c792357c99f49b,
BundleFileType::Ugg => 0x712d6e3dd1024c9c,
BundleFileType::Unit => 0xe0a48d0be9a7453f,
BundleFileType::Upb => 0xa99510c6e86dd3c2,
BundleFileType::VectorField => 0xf7505933166d6755,
BundleFileType::Wav => 0x786f65c00a816b19,
BundleFileType::WwiseBank => 0x535a7bd3e650d799,
BundleFileType::WwiseDep => 0xaf32095c82f2b070,
BundleFileType::WwiseEvent => 0xaabdd317b58dfc8a,
BundleFileType::WwiseMetadata => 0xd50a8b7e1c82b110,
BundleFileType::WwiseStream => 0x504b55235d21440e,
BundleFileType::Xml => 0x76015845a6003765,
BundleFileType::Unknown(hash) => hash.into(),
}
}
}
impl From<BundleFileType> for Murmur64 {
fn from(t: BundleFileType) -> Murmur64 {
let hash: u64 = t.into();
Murmur64::from(hash)
}
}
impl std::fmt::Display for BundleFileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ext_name())
}
}
#[derive(Debug)] #[derive(Debug)]
struct BundleFileHeader { struct BundleFileHeader {
@ -515,7 +120,7 @@ pub struct BundleFile {
} }
impl BundleFile { impl BundleFile {
pub fn new(name: String, file_type: BundleFileType) -> Self { pub fn new(name: impl Into<IdString64>, file_type: BundleFileType) -> Self {
Self { Self {
file_type, file_type,
name: name.into(), name: name.into(),
@ -647,20 +252,15 @@ impl BundleFile {
Ok(w.into_inner()) Ok(w.into_inner())
} }
#[tracing::instrument(name = "File::from_sjson", skip(sjson))] #[tracing::instrument("File::from_sjson", skip(sjson, name), fields(name = %name.display()))]
pub async fn from_sjson<P, S>( pub async fn from_sjson(
name: String, name: IdString64,
file_type: BundleFileType, file_type: BundleFileType,
sjson: S, sjson: impl AsRef<str>,
root: P, root: impl AsRef<Path> + std::fmt::Debug,
) -> Result<Self> ) -> Result<Self> {
where
P: AsRef<Path> + std::fmt::Debug,
S: AsRef<str>,
{
match file_type { match file_type {
BundleFileType::Lua => lua::compile(name.clone(), sjson) BundleFileType::Lua => lua::compile(name, sjson).wrap_err("Failed to compile Lua file"),
.wrap_err_with(|| format!("Failed to compile Lua file '{}'", name)),
BundleFileType::Unknown(_) => { BundleFileType::Unknown(_) => {
eyre::bail!("Unknown file type. Cannot compile from SJSON"); eyre::bail!("Unknown file type. Cannot compile from SJSON");
} }
@ -699,10 +299,7 @@ impl BundleFile {
s s
} }
pub fn matches_name<S>(&self, name: S) -> bool pub fn matches_name(&self, name: impl Into<IdString64>) -> bool {
where
S: Into<IdString64>,
{
let name = name.into(); let name = name.into();
if self.name == name { if self.name == name {
return true; return true;

View file

@ -0,0 +1,400 @@
use color_eyre::{eyre, Result};
use serde::Serialize;
use crate::murmur::Murmur64;
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
pub enum BundleFileType {
Animation,
AnimationCurves,
Apb,
BakedLighting,
Bik,
BlendSet,
Bones,
Chroma,
CommonPackage,
Config,
Crypto,
Data,
Entity,
Flow,
Font,
Ies,
Ini,
Input,
Ivf,
Keys,
Level,
Lua,
Material,
Mod,
MouseCursor,
NavData,
NetworkConfig,
OddleNet,
Package,
Particles,
PhysicsProperties,
RenderConfig,
RtPipeline,
Scene,
Shader,
ShaderLibrary,
ShaderLibraryGroup,
ShadingEnvionmentMapping,
ShadingEnvironment,
Slug,
SlugAlbum,
SoundEnvironment,
SpuJob,
StateMachine,
StaticPVS,
Strings,
SurfaceProperties,
Texture,
TimpaniBank,
TimpaniMaster,
Tome,
Ugg,
Unit,
Upb,
VectorField,
Wav,
WwiseBank,
WwiseDep,
WwiseEvent,
WwiseMetadata,
WwiseStream,
Xml,
Unknown(Murmur64),
}
impl BundleFileType {
pub fn ext_name(&self) -> String {
match self {
BundleFileType::AnimationCurves => String::from("animation_curves"),
BundleFileType::Animation => String::from("animation"),
BundleFileType::Apb => String::from("apb"),
BundleFileType::BakedLighting => String::from("baked_lighting"),
BundleFileType::Bik => String::from("bik"),
BundleFileType::BlendSet => String::from("blend_set"),
BundleFileType::Bones => String::from("bones"),
BundleFileType::Chroma => String::from("chroma"),
BundleFileType::CommonPackage => String::from("common_package"),
BundleFileType::Config => String::from("config"),
BundleFileType::Crypto => String::from("crypto"),
BundleFileType::Data => String::from("data"),
BundleFileType::Entity => String::from("entity"),
BundleFileType::Flow => String::from("flow"),
BundleFileType::Font => String::from("font"),
BundleFileType::Ies => String::from("ies"),
BundleFileType::Ini => String::from("ini"),
BundleFileType::Input => String::from("input"),
BundleFileType::Ivf => String::from("ivf"),
BundleFileType::Keys => String::from("keys"),
BundleFileType::Level => String::from("level"),
BundleFileType::Lua => String::from("lua"),
BundleFileType::Material => String::from("material"),
BundleFileType::Mod => String::from("mod"),
BundleFileType::MouseCursor => String::from("mouse_cursor"),
BundleFileType::NavData => String::from("nav_data"),
BundleFileType::NetworkConfig => String::from("network_config"),
BundleFileType::OddleNet => String::from("oodle_net"),
BundleFileType::Package => String::from("package"),
BundleFileType::Particles => String::from("particles"),
BundleFileType::PhysicsProperties => String::from("physics_properties"),
BundleFileType::RenderConfig => String::from("render_config"),
BundleFileType::RtPipeline => String::from("rt_pipeline"),
BundleFileType::Scene => String::from("scene"),
BundleFileType::ShaderLibraryGroup => String::from("shader_library_group"),
BundleFileType::ShaderLibrary => String::from("shader_library"),
BundleFileType::Shader => String::from("shader"),
BundleFileType::ShadingEnvionmentMapping => String::from("shading_environment_mapping"),
BundleFileType::ShadingEnvironment => String::from("shading_environment"),
BundleFileType::SlugAlbum => String::from("slug_album"),
BundleFileType::Slug => String::from("slug"),
BundleFileType::SoundEnvironment => String::from("sound_environment"),
BundleFileType::SpuJob => String::from("spu_job"),
BundleFileType::StateMachine => String::from("state_machine"),
BundleFileType::StaticPVS => String::from("static_pvs"),
BundleFileType::Strings => String::from("strings"),
BundleFileType::SurfaceProperties => String::from("surface_properties"),
BundleFileType::Texture => String::from("texture"),
BundleFileType::TimpaniBank => String::from("timpani_bank"),
BundleFileType::TimpaniMaster => String::from("timpani_master"),
BundleFileType::Tome => String::from("tome"),
BundleFileType::Ugg => String::from("ugg"),
BundleFileType::Unit => String::from("unit"),
BundleFileType::Upb => String::from("upb"),
BundleFileType::VectorField => String::from("vector_field"),
BundleFileType::Wav => String::from("wav"),
BundleFileType::WwiseBank => String::from("wwise_bank"),
BundleFileType::WwiseDep => String::from("wwise_dep"),
BundleFileType::WwiseEvent => String::from("wwise_event"),
BundleFileType::WwiseMetadata => String::from("wwise_metadata"),
BundleFileType::WwiseStream => String::from("wwise_stream"),
BundleFileType::Xml => String::from("xml"),
BundleFileType::Unknown(s) => format!("{s:016X}"),
}
}
pub fn decompiled_ext_name(&self) -> String {
match self {
BundleFileType::Texture => String::from("dds"),
BundleFileType::WwiseBank => String::from("bnk"),
BundleFileType::WwiseStream => String::from("ogg"),
_ => self.ext_name(),
}
}
pub fn hash(&self) -> Murmur64 {
Murmur64::from(*self)
}
}
impl std::str::FromStr for BundleFileType {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let val = match s {
"animation_curves" => BundleFileType::AnimationCurves,
"animation" => BundleFileType::Animation,
"apb" => BundleFileType::Apb,
"baked_lighting" => BundleFileType::BakedLighting,
"bik" => BundleFileType::Bik,
"blend_set" => BundleFileType::BlendSet,
"bones" => BundleFileType::Bones,
"chroma" => BundleFileType::Chroma,
"common_package" => BundleFileType::CommonPackage,
"config" => BundleFileType::Config,
"crypto" => BundleFileType::Crypto,
"data" => BundleFileType::Data,
"entity" => BundleFileType::Entity,
"flow" => BundleFileType::Flow,
"font" => BundleFileType::Font,
"ies" => BundleFileType::Ies,
"ini" => BundleFileType::Ini,
"input" => BundleFileType::Input,
"ivf" => BundleFileType::Ivf,
"keys" => BundleFileType::Keys,
"level" => BundleFileType::Level,
"lua" => BundleFileType::Lua,
"material" => BundleFileType::Material,
"mod" => BundleFileType::Mod,
"mouse_cursor" => BundleFileType::MouseCursor,
"nav_data" => BundleFileType::NavData,
"network_config" => BundleFileType::NetworkConfig,
"oodle_net" => BundleFileType::OddleNet,
"package" => BundleFileType::Package,
"particles" => BundleFileType::Particles,
"physics_properties" => BundleFileType::PhysicsProperties,
"render_config" => BundleFileType::RenderConfig,
"rt_pipeline" => BundleFileType::RtPipeline,
"scene" => BundleFileType::Scene,
"shader_library_group" => BundleFileType::ShaderLibraryGroup,
"shader_library" => BundleFileType::ShaderLibrary,
"shader" => BundleFileType::Shader,
"shading_environment_mapping" => BundleFileType::ShadingEnvionmentMapping,
"shading_environment" => BundleFileType::ShadingEnvironment,
"slug_album" => BundleFileType::SlugAlbum,
"slug" => BundleFileType::Slug,
"sound_environment" => BundleFileType::SoundEnvironment,
"spu_job" => BundleFileType::SpuJob,
"state_machine" => BundleFileType::StateMachine,
"static_pvs" => BundleFileType::StaticPVS,
"strings" => BundleFileType::Strings,
"surface_properties" => BundleFileType::SurfaceProperties,
"texture" => BundleFileType::Texture,
"timpani_bank" => BundleFileType::TimpaniBank,
"timpani_master" => BundleFileType::TimpaniMaster,
"tome" => BundleFileType::Tome,
"ugg" => BundleFileType::Ugg,
"unit" => BundleFileType::Unit,
"upb" => BundleFileType::Upb,
"vector_field" => BundleFileType::VectorField,
"wav" => BundleFileType::Wav,
"wwise_bank" => BundleFileType::WwiseBank,
"wwise_dep" => BundleFileType::WwiseDep,
"wwise_event" => BundleFileType::WwiseEvent,
"wwise_metadata" => BundleFileType::WwiseMetadata,
"wwise_stream" => BundleFileType::WwiseStream,
"xml" => BundleFileType::Xml,
s => eyre::bail!("Unknown type string '{}'", s),
};
Ok(val)
}
}
impl Serialize for BundleFileType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let value = self.ext_name();
value.serialize(serializer)
}
}
impl From<Murmur64> for BundleFileType {
fn from(value: Murmur64) -> Self {
Self::from(Into::<u64>::into(value))
}
}
impl From<u64> for BundleFileType {
fn from(hash: u64) -> BundleFileType {
match hash {
0x931e336d7646cc26 => BundleFileType::Animation,
0xdcfb9e18fff13984 => BundleFileType::AnimationCurves,
0x3eed05ba83af5090 => BundleFileType::Apb,
0x7ffdb779b04e4ed1 => BundleFileType::BakedLighting,
0xaa5965f03029fa18 => BundleFileType::Bik,
0xe301e8af94e3b5a3 => BundleFileType::BlendSet,
0x18dead01056b72e9 => BundleFileType::Bones,
0xb7893adf7567506a => BundleFileType::Chroma,
0xfe9754bd19814a47 => BundleFileType::CommonPackage,
0x82645835e6b73232 => BundleFileType::Config,
0x69108ded1e3e634b => BundleFileType::Crypto,
0x8fd0d44d20650b68 => BundleFileType::Data,
0x9831ca893b0d087d => BundleFileType::Entity,
0x92d3ee038eeb610d => BundleFileType::Flow,
0x9efe0a916aae7880 => BundleFileType::Font,
0x8f7d5a2c0f967655 => BundleFileType::Ies,
0xd526a27da14f1dc5 => BundleFileType::Ini,
0x2bbcabe5074ade9e => BundleFileType::Input,
0xfa4a8e091a91201e => BundleFileType::Ivf,
0xa62f9297dc969e85 => BundleFileType::Keys,
0x2a690fd348fe9ac5 => BundleFileType::Level,
0xa14e8dfa2cd117e2 => BundleFileType::Lua,
0xeac0b497876adedf => BundleFileType::Material,
0x3fcdd69156a46417 => BundleFileType::Mod,
0xb277b11fe4a61d37 => BundleFileType::MouseCursor,
0x169de9566953d264 => BundleFileType::NavData,
0x3b1fa9e8f6bac374 => BundleFileType::NetworkConfig,
0xb0f2c12eb107f4d8 => BundleFileType::OddleNet,
0xad9c6d9ed1e5e77a => BundleFileType::Package,
0xa8193123526fad64 => BundleFileType::Particles,
0xbf21403a3ab0bbb1 => BundleFileType::PhysicsProperties,
0x27862fe24795319c => BundleFileType::RenderConfig,
0x9ca183c2d0e76dee => BundleFileType::RtPipeline,
0x9d0a795bfe818d19 => BundleFileType::Scene,
0xcce8d5b5f5ae333f => BundleFileType::Shader,
0xe5ee32a477239a93 => BundleFileType::ShaderLibrary,
0x9e5c3cc74575aeb5 => BundleFileType::ShaderLibraryGroup,
0x250e0a11ac8e26f8 => BundleFileType::ShadingEnvionmentMapping,
0xfe73c7dcff8a7ca5 => BundleFileType::ShadingEnvironment,
0xa27b4d04a9ba6f9e => BundleFileType::Slug,
0xe9fc9ea7042e5ec0 => BundleFileType::SlugAlbum,
0xd8b27864a97ffdd7 => BundleFileType::SoundEnvironment,
0xf97af9983c05b950 => BundleFileType::SpuJob,
0xa486d4045106165c => BundleFileType::StateMachine,
0xe3f0baa17d620321 => BundleFileType::StaticPVS,
0x0d972bab10b40fd3 => BundleFileType::Strings,
0xad2d3fa30d9ab394 => BundleFileType::SurfaceProperties,
0xcd4238c6a0c69e32 => BundleFileType::Texture,
0x99736be1fff739a4 => BundleFileType::TimpaniBank,
0x00a3e6c59a2b9c6c => BundleFileType::TimpaniMaster,
0x19c792357c99f49b => BundleFileType::Tome,
0x712d6e3dd1024c9c => BundleFileType::Ugg,
0xe0a48d0be9a7453f => BundleFileType::Unit,
0xa99510c6e86dd3c2 => BundleFileType::Upb,
0xf7505933166d6755 => BundleFileType::VectorField,
0x786f65c00a816b19 => BundleFileType::Wav,
0x535a7bd3e650d799 => BundleFileType::WwiseBank,
0xaf32095c82f2b070 => BundleFileType::WwiseDep,
0xaabdd317b58dfc8a => BundleFileType::WwiseEvent,
0xd50a8b7e1c82b110 => BundleFileType::WwiseMetadata,
0x504b55235d21440e => BundleFileType::WwiseStream,
0x76015845a6003765 => BundleFileType::Xml,
_ => BundleFileType::Unknown(Murmur64::from(hash)),
}
}
}
impl From<BundleFileType> for u64 {
fn from(t: BundleFileType) -> u64 {
match t {
BundleFileType::Animation => 0x931e336d7646cc26,
BundleFileType::AnimationCurves => 0xdcfb9e18fff13984,
BundleFileType::Apb => 0x3eed05ba83af5090,
BundleFileType::BakedLighting => 0x7ffdb779b04e4ed1,
BundleFileType::Bik => 0xaa5965f03029fa18,
BundleFileType::BlendSet => 0xe301e8af94e3b5a3,
BundleFileType::Bones => 0x18dead01056b72e9,
BundleFileType::Chroma => 0xb7893adf7567506a,
BundleFileType::CommonPackage => 0xfe9754bd19814a47,
BundleFileType::Config => 0x82645835e6b73232,
BundleFileType::Crypto => 0x69108ded1e3e634b,
BundleFileType::Data => 0x8fd0d44d20650b68,
BundleFileType::Entity => 0x9831ca893b0d087d,
BundleFileType::Flow => 0x92d3ee038eeb610d,
BundleFileType::Font => 0x9efe0a916aae7880,
BundleFileType::Ies => 0x8f7d5a2c0f967655,
BundleFileType::Ini => 0xd526a27da14f1dc5,
BundleFileType::Input => 0x2bbcabe5074ade9e,
BundleFileType::Ivf => 0xfa4a8e091a91201e,
BundleFileType::Keys => 0xa62f9297dc969e85,
BundleFileType::Level => 0x2a690fd348fe9ac5,
BundleFileType::Lua => 0xa14e8dfa2cd117e2,
BundleFileType::Material => 0xeac0b497876adedf,
BundleFileType::Mod => 0x3fcdd69156a46417,
BundleFileType::MouseCursor => 0xb277b11fe4a61d37,
BundleFileType::NavData => 0x169de9566953d264,
BundleFileType::NetworkConfig => 0x3b1fa9e8f6bac374,
BundleFileType::OddleNet => 0xb0f2c12eb107f4d8,
BundleFileType::Package => 0xad9c6d9ed1e5e77a,
BundleFileType::Particles => 0xa8193123526fad64,
BundleFileType::PhysicsProperties => 0xbf21403a3ab0bbb1,
BundleFileType::RenderConfig => 0x27862fe24795319c,
BundleFileType::RtPipeline => 0x9ca183c2d0e76dee,
BundleFileType::Scene => 0x9d0a795bfe818d19,
BundleFileType::Shader => 0xcce8d5b5f5ae333f,
BundleFileType::ShaderLibrary => 0xe5ee32a477239a93,
BundleFileType::ShaderLibraryGroup => 0x9e5c3cc74575aeb5,
BundleFileType::ShadingEnvionmentMapping => 0x250e0a11ac8e26f8,
BundleFileType::ShadingEnvironment => 0xfe73c7dcff8a7ca5,
BundleFileType::Slug => 0xa27b4d04a9ba6f9e,
BundleFileType::SlugAlbum => 0xe9fc9ea7042e5ec0,
BundleFileType::SoundEnvironment => 0xd8b27864a97ffdd7,
BundleFileType::SpuJob => 0xf97af9983c05b950,
BundleFileType::StateMachine => 0xa486d4045106165c,
BundleFileType::StaticPVS => 0xe3f0baa17d620321,
BundleFileType::Strings => 0x0d972bab10b40fd3,
BundleFileType::SurfaceProperties => 0xad2d3fa30d9ab394,
BundleFileType::Texture => 0xcd4238c6a0c69e32,
BundleFileType::TimpaniBank => 0x99736be1fff739a4,
BundleFileType::TimpaniMaster => 0x00a3e6c59a2b9c6c,
BundleFileType::Tome => 0x19c792357c99f49b,
BundleFileType::Ugg => 0x712d6e3dd1024c9c,
BundleFileType::Unit => 0xe0a48d0be9a7453f,
BundleFileType::Upb => 0xa99510c6e86dd3c2,
BundleFileType::VectorField => 0xf7505933166d6755,
BundleFileType::Wav => 0x786f65c00a816b19,
BundleFileType::WwiseBank => 0x535a7bd3e650d799,
BundleFileType::WwiseDep => 0xaf32095c82f2b070,
BundleFileType::WwiseEvent => 0xaabdd317b58dfc8a,
BundleFileType::WwiseMetadata => 0xd50a8b7e1c82b110,
BundleFileType::WwiseStream => 0x504b55235d21440e,
BundleFileType::Xml => 0x76015845a6003765,
BundleFileType::Unknown(hash) => hash.into(),
}
}
}
impl From<BundleFileType> for Murmur64 {
fn from(t: BundleFileType) -> Murmur64 {
let hash: u64 = t.into();
Murmur64::from(hash)
}
}
impl std::fmt::Display for BundleFileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ext_name())
}
}

View file

@ -12,8 +12,10 @@ use crate::murmur::{HashGroup, IdString64, Murmur64};
pub(crate) mod database; pub(crate) mod database;
pub(crate) mod file; pub(crate) mod file;
pub(crate) mod filetype;
pub use file::{BundleFile, BundleFileType, BundleFileVariant}; pub use file::{BundleFile, BundleFileVariant};
pub use filetype::BundleFileType;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
enum BundleFormat { enum BundleFormat {
@ -235,7 +237,7 @@ impl Bundle {
// Ceiling division (or division toward infinity) to calculate // Ceiling division (or division toward infinity) to calculate
// the number of chunks required to fit the unpacked data. // the number of chunks required to fit the unpacked data.
let num_chunks = (unpacked_data.len() + CHUNK_SIZE - 1) / CHUNK_SIZE; let num_chunks = unpacked_data.len().div_ceil(CHUNK_SIZE);
tracing::trace!(num_chunks); tracing::trace!(num_chunks);
w.write_u32(num_chunks as u32)?; w.write_u32(num_chunks as u32)?;

View file

@ -15,6 +15,7 @@ use tokio::fs;
use crate::binary::sync::ReadExt; use crate::binary::sync::ReadExt;
use crate::binary::sync::WriteExt; use crate::binary::sync::WriteExt;
use crate::bundle::file::{BundleFileVariant, UserFile}; use crate::bundle::file::{BundleFileVariant, UserFile};
use crate::murmur::IdString64;
use crate::{BundleFile, BundleFileType}; use crate::{BundleFile, BundleFileType};
const BITSQUID_LUAJIT_HEADER: u32 = 0x8253461B; const BITSQUID_LUAJIT_HEADER: u32 = 0x8253461B;
@ -117,26 +118,22 @@ where
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn compile<S, C>(name: S, code: C) -> Result<BundleFile> pub fn compile(name: impl Into<IdString64>, code: impl AsRef<str>) -> Result<BundleFile> {
where
S: Into<String>,
C: AsRef<str>,
{
let name = name.into(); let name = name.into();
let code = code.as_ref(); let code = code.as_ref();
tracing::trace!( tracing::trace!(
"Compiling '{}', {} bytes of code", "Compiling '{}', {} bytes of code",
name, name.display(),
code.as_bytes().len() code.len()
); );
let bytecode = unsafe { let bytecode = unsafe {
let state = lua::luaL_newstate(); let state = lua::luaL_newstate();
lua::luaL_openlibs(state); lua::luaL_openlibs(state);
let name = CString::new(format!("@{name}").into_bytes()) let name = CString::new(format!("@{}", name.display()).into_bytes())
.wrap_err_with(|| format!("Cannot convert name into CString: {}", name))?; .wrap_err_with(|| format!("Cannot convert name into CString: {}", name.display()))?;
match lua::luaL_loadbuffer( match lua::luaL_loadbuffer(
state, state,
code.as_ptr() as _, code.as_ptr() as _,
@ -159,10 +156,10 @@ where
} }
_ => unreachable!(), _ => unreachable!(),
} }
lua::lua_setglobal(state, b"fn\0".as_ptr() as _); lua::lua_setglobal(state, c"fn".as_ptr());
let run = b"return string.dump(fn, false)\0"; let run = c"return string.dump(fn, false)";
match lua::luaL_loadstring(state, run.as_ptr() as _) as u32 { match lua::luaL_loadstring(state, run.as_ptr()) as u32 {
lua::LUA_OK => {} lua::LUA_OK => {}
lua::LUA_ERRSYNTAX => { lua::LUA_ERRSYNTAX => {
let err = lua::lua_tostring(state, -1); let err = lua::lua_tostring(state, -1);

View file

@ -7,13 +7,22 @@ use std::str::FromStr;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use color_eyre::eyre::{self, Context}; use color_eyre::eyre::{self, Context};
use color_eyre::Result; use color_eyre::Result;
use path_slash::PathBufExt;
use tokio::fs; use tokio::fs;
use crate::binary::sync::{ReadExt, WriteExt}; use crate::binary::sync::{ReadExt, WriteExt};
use crate::bundle::file::{BundleFileType, UserFile}; use crate::bundle::file::UserFile;
use crate::murmur::{HashGroup, Murmur64}; use crate::bundle::filetype::BundleFileType;
use crate::murmur::{HashGroup, IdString64, Murmur64};
/// Resolves a relative path that might contain wildcards into a list of
/// paths that exist on disk and match that wildcard.
/// This is similar to globbing in Unix shells, but with much less features.
///
/// The only wilcard character allowed is `*`, and only at the end of the string,
/// where it matches all files recursively in that directory.
///
/// `t` is an optional extension name, that may be used to force a wildcard
/// path to only match that file type `t`.
#[tracing::instrument] #[tracing::instrument]
#[async_recursion] #[async_recursion]
async fn resolve_wildcard<P1, P2>( async fn resolve_wildcard<P1, P2>(
@ -90,12 +99,12 @@ where
Ok(paths) Ok(paths)
} }
type PackageType = HashMap<BundleFileType, HashSet<PathBuf>>; type PackageType = HashMap<BundleFileType, HashSet<String>>;
type PackageDefinition = HashMap<String, HashSet<String>>; type PackageDefinition = HashMap<String, HashSet<String>>;
#[derive(Default)] #[derive(Default)]
pub struct Package { pub struct Package {
_name: String, _name: IdString64,
_root: PathBuf, _root: PathBuf,
inner: PackageType, inner: PackageType,
flags: u8, flags: u8,
@ -116,9 +125,9 @@ impl DerefMut for Package {
} }
impl Package { impl Package {
pub fn new(name: String, root: PathBuf) -> Self { pub fn new(name: impl Into<IdString64>, root: PathBuf) -> Self {
Self { Self {
_name: name, _name: name.into(),
_root: root, _root: root,
inner: Default::default(), inner: Default::default(),
flags: 1, flags: 1,
@ -129,17 +138,22 @@ impl Package {
self.values().fold(0, |total, files| total + files.len()) self.values().fold(0, |total, files| total + files.len())
} }
pub fn add_file<P: Into<PathBuf>>(&mut self, file_type: BundleFileType, name: P) { pub fn add_file(&mut self, file_type: BundleFileType, name: impl Into<String>) {
self.inner.entry(file_type).or_default().insert(name.into()); self.inner.entry(file_type).or_default().insert(name.into());
} }
#[tracing::instrument("Package::from_sjson", skip(sjson), fields(sjson_len = sjson.as_ref().len()))] #[tracing::instrument("Package::from_sjson", skip(sjson), fields(sjson_len = sjson.as_ref().len()))]
pub async fn from_sjson<P, S>(sjson: S, name: String, root: P) -> Result<Self> pub async fn from_sjson<P, S>(
sjson: S,
name: impl Into<IdString64> + std::fmt::Debug,
root: P,
) -> Result<Self>
where where
P: AsRef<Path> + std::fmt::Debug, P: AsRef<Path> + std::fmt::Debug,
S: AsRef<str>, S: AsRef<str>,
{ {
let root = root.as_ref(); let root = root.as_ref();
let name = name.into();
let definition: PackageDefinition = serde_sjson::from_str(sjson.as_ref())?; let definition: PackageDefinition = serde_sjson::from_str(sjson.as_ref())?;
let mut inner: PackageType = Default::default(); let mut inner: PackageType = Default::default();
@ -173,7 +187,11 @@ impl Package {
continue; continue;
}; };
inner.entry(t).or_default().insert(path); tracing::debug!("Adding file {}", path.display());
inner
.entry(t)
.or_default()
.insert(path.display().to_string());
} }
} }
} }
@ -192,11 +210,9 @@ impl Package {
pub fn to_sjson(&self) -> Result<String> { pub fn to_sjson(&self) -> Result<String> {
let mut map: PackageDefinition = Default::default(); let mut map: PackageDefinition = Default::default();
for (t, paths) in self.iter() { for (t, names) in self.iter() {
for path in paths.iter() { for name in names.iter() {
map.entry(t.ext_name()) map.entry(t.ext_name()).or_default().insert(name.clone());
.or_default()
.insert(path.display().to_string());
} }
} }
@ -222,11 +238,11 @@ impl Package {
for _ in 0..file_count { for _ in 0..file_count {
let t = BundleFileType::from(r.read_u64()?); let t = BundleFileType::from(r.read_u64()?);
let hash = Murmur64::from(r.read_u64()?); let hash = Murmur64::from(r.read_u64()?);
let path = ctx.lookup_hash(hash, HashGroup::Filename); let name = ctx.lookup_hash(hash, HashGroup::Filename);
inner inner
.entry(t) .entry(t)
.or_default() .or_default()
.insert(PathBuf::from(path.display().to_string())); .insert(name.display().to_string());
} }
let flags = r.read_u8()?; let flags = r.read_u8()?;
@ -239,7 +255,7 @@ impl Package {
let pkg = Self { let pkg = Self {
inner, inner,
_name: name, _name: name.into(),
_root: PathBuf::new(), _root: PathBuf::new(),
flags, flags,
}; };
@ -255,12 +271,10 @@ impl Package {
w.write_u32(0x2b)?; w.write_u32(0x2b)?;
w.write_u32(self.values().flatten().count() as u32)?; w.write_u32(self.values().flatten().count() as u32)?;
for (t, paths) in self.iter() { for (t, names) in self.iter() {
for path in paths.iter() { for name in names.iter() {
w.write_u64(t.hash().into())?; w.write_u64(t.hash().into())?;
w.write_u64(Murmur64::hash(name.as_bytes()).into())?;
let hash = Murmur64::hash(path.to_slash_lossy().as_bytes());
w.write_u64(hash.into())?;
} }
} }
@ -280,17 +294,11 @@ where
Ok(vec![UserFile::new(s.into_bytes())]) Ok(vec![UserFile::new(s.into_bytes())])
} }
// #[tracing::instrument(skip_all)]
// pub fn compile(_ctx: &crate::Context, data: String) -> Result<Vec<u8>> {
// let pkg = Package::from_sjson(data)?;
// pkg.to_binary()
// }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::path::PathBuf; use std::path::PathBuf;
use crate::BundleFileType; use crate::bundle::filetype::BundleFileType;
use super::resolve_wildcard; use super::resolve_wildcard;
use super::Package; use super::Package;

View file

@ -28,10 +28,14 @@ impl Language {
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct Strings(HashMap<String, HashMap<Language, String>>); pub struct Strings(HashMap<String, HashMap<Language, String>>);
#[inline(always)]
fn read_string<R>(r: R) -> Result<String> fn read_string<R>(r: R) -> Result<String>
where where
R: Read, R: Read,
{ {
// We can safely ignore the warning here, as all data is already in memory, and no additional
// `BufReader` should be needed.
#[allow(clippy::unbuffered_bytes)]
r.bytes() r.bytes()
.take_while(|b| b.as_ref().map(|b| *b != 0).unwrap_or(false)) .take_while(|b| b.as_ref().map(|b| *b != 0).unwrap_or(false))
.map(|b| b.map_err(Report::new)) .map(|b| b.map_err(Report::new))
@ -41,7 +45,7 @@ where
impl Strings { impl Strings {
#[tracing::instrument(skip_all, fields(languages = variants.len()))] #[tracing::instrument(skip_all, fields(languages = variants.len()))]
pub fn from_variants(ctx: &crate::Context, variants: &Vec<BundleFileVariant>) -> Result<Self> { pub fn from_variants(ctx: &crate::Context, variants: &[BundleFileVariant]) -> Result<Self> {
let mut map: HashMap<String, HashMap<Language, String>> = HashMap::new(); let mut map: HashMap<String, HashMap<Language, String>> = HashMap::new();
for (i, variant) in variants.iter().enumerate() { for (i, variant) in variants.iter().enumerate() {
@ -76,7 +80,7 @@ impl Strings {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn decompile(ctx: &crate::Context, variants: &Vec<BundleFileVariant>) -> Result<Vec<UserFile>> { pub fn decompile(ctx: &crate::Context, variants: &[BundleFileVariant]) -> Result<Vec<UserFile>> {
let strings = Strings::from_variants(ctx, variants)?; let strings = Strings::from_variants(ctx, variants)?;
let content = strings.to_sjson()?; let content = strings.to_sjson()?;

View file

@ -147,14 +147,14 @@ impl Dictionary {
Ok(()) Ok(())
} }
pub fn add(&mut self, value: String, group: HashGroup) { pub fn add(&mut self, value: impl AsRef<[u8]>, group: HashGroup) {
let long = Murmur64::from(murmurhash64::hash(value.as_bytes(), SEED as u64)); let long = Murmur64::from(murmurhash64::hash(value.as_ref(), SEED as u64));
let short = Murmur32::from(murmurhash64::hash32(value.as_bytes(), SEED)); let short = Murmur32::from(murmurhash64::hash32(value.as_ref(), SEED));
let entry = Entry { let entry = Entry {
long, long,
short, short,
value, value: String::from_utf8_lossy(value.as_ref()).to_string(),
group, group,
}; };

View file

@ -0,0 +1,162 @@
use std::fmt;
use serde::{Deserializer, Serializer};
use super::Murmur32;
// This type encodes the fact that when reading in a bundle, we don't always have a dictionary
// entry for every hash in there. So we do want to have the real string available when needed,
// but at the same time retain the original hash information for when we don't.
// This is especially important when wanting to write back the read bundle, as the hashes need to
// stay the same.
// The previous system of always turning hashes into strings worked well for the purpose of
// displaying hashes, but would have made it very hard to turn a stringyfied hash back into
// an actual hash.
#[derive(Clone, Debug, Eq)]
pub enum IdString32 {
Hash(Murmur32),
String(String),
}
impl IdString32 {
pub fn to_murmur32(&self) -> Murmur32 {
match self {
Self::Hash(hash) => *hash,
Self::String(s) => Murmur32::hash(s.as_bytes()),
}
}
pub fn display(&self) -> IdString32Display {
let s = match self {
IdString32::Hash(hash) => hash.to_string(),
IdString32::String(s) => s.clone(),
};
IdString32Display(s)
}
pub fn is_string(&self) -> bool {
match self {
IdString32::Hash(_) => false,
IdString32::String(_) => true,
}
}
pub fn is_hash(&self) -> bool {
match self {
IdString32::Hash(_) => true,
IdString32::String(_) => false,
}
}
}
impl From<String> for IdString32 {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<u32> for IdString32 {
fn from(value: u32) -> Self {
Self::Hash(value.into())
}
}
impl From<IdString32> for u32 {
fn from(value: IdString32) -> Self {
value.to_murmur32().into()
}
}
impl From<Murmur32> for IdString32 {
fn from(value: Murmur32) -> Self {
Self::Hash(value)
}
}
impl From<IdString32> for Murmur32 {
fn from(value: IdString32) -> Self {
value.to_murmur32()
}
}
impl PartialEq for IdString32 {
fn eq(&self, other: &Self) -> bool {
self.to_murmur32() == other.to_murmur32()
}
}
impl std::hash::Hash for IdString32 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.to_murmur32().into());
}
}
impl serde::Serialize for IdString32 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(self.to_murmur32().into())
}
}
struct IdString32Visitor;
impl<'de> serde::de::Visitor<'de> for IdString32Visitor {
type Value = IdString32;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an u32 or a string")
}
fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString32::Hash(value.into()))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString32::String(v.to_string()))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString32::String(v))
}
}
impl<'de> serde::Deserialize<'de> for IdString32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u32(IdString32Visitor)
}
}
pub struct IdString32Display(String);
impl std::fmt::Display for IdString32Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::UpperHex for IdString32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::UpperHex::fmt(&self.to_murmur32(), f)
}
}
impl std::fmt::LowerHex for IdString32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::LowerHex::fmt(&self.to_murmur32(), f)
}
}

View file

@ -0,0 +1,175 @@
use std::{fmt, path::Path};
use path_slash::PathExt as _;
use serde::{Deserializer, Serializer};
use super::Murmur64;
// This type encodes the fact that when reading in a bundle, we don't always have a dictionary
// entry for every hash in there. So we do want to have the real string available when needed,
// but at the same time retain the original hash information for when we don't.
// This is especially important when wanting to write back the read bundle, as the hashes need to
// stay the same.
// The previous system of always turning hashes into strings worked well for the purpose of
// displaying hashes, but would have made it very hard to turn a stringyfied hash back into
// an actual hash.
#[derive(Clone, Debug, Eq)]
pub enum IdString64 {
Hash(Murmur64),
String(String),
}
impl IdString64 {
pub fn to_murmur64(&self) -> Murmur64 {
match self {
Self::Hash(hash) => *hash,
Self::String(s) => Murmur64::hash(s.as_bytes()),
}
}
pub fn display(&self) -> IdString64Display {
let s = match self {
IdString64::Hash(hash) => hash.to_string(),
IdString64::String(s) => s.clone(),
};
IdString64Display(s)
}
pub fn is_string(&self) -> bool {
match self {
IdString64::Hash(_) => false,
IdString64::String(_) => true,
}
}
pub fn is_hash(&self) -> bool {
match self {
IdString64::Hash(_) => true,
IdString64::String(_) => false,
}
}
// Would love to have this as a proper `impl From`, but
// rustc will complain that it overlaps with the `impl From<Into<String>>`.
pub fn from_path(p: impl AsRef<Path>) -> Self {
Self::String(p.as_ref().to_slash_lossy().to_string())
}
}
impl From<String> for IdString64 {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<u64> for IdString64 {
fn from(value: u64) -> Self {
Self::Hash(value.into())
}
}
impl From<Murmur64> for IdString64 {
fn from(value: Murmur64) -> Self {
Self::Hash(value)
}
}
impl From<IdString64> for Murmur64 {
fn from(value: IdString64) -> Self {
value.to_murmur64()
}
}
impl From<IdString64> for u64 {
fn from(value: IdString64) -> Self {
value.to_murmur64().into()
}
}
impl Default for IdString64 {
fn default() -> Self {
Self::Hash(0.into())
}
}
impl PartialEq for IdString64 {
fn eq(&self, other: &Self) -> bool {
self.to_murmur64() == other.to_murmur64()
}
}
impl std::hash::Hash for IdString64 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.to_murmur64().into());
}
}
impl serde::Serialize for IdString64 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(self.to_murmur64().into())
}
}
struct IdString64Visitor;
impl<'de> serde::de::Visitor<'de> for IdString64Visitor {
type Value = IdString64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an u64 or a string")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::Hash(value.into()))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::String(v.to_string()))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::String(v))
}
}
impl<'de> serde::Deserialize<'de> for IdString64 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u64(IdString64Visitor)
}
}
pub struct IdString64Display(String);
impl std::fmt::Display for IdString64Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::UpperHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::UpperHex::fmt(&self.to_murmur64(), f)
}
}
impl std::fmt::LowerHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::LowerHex::fmt(&self.to_murmur64(), f)
}
}

View file

@ -8,6 +8,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
mod dictionary; mod dictionary;
// Currently unused // Currently unused
// mod murmurhash32; // mod murmurhash32;
mod idstring32;
mod idstring64;
mod murmurhash64; mod murmurhash64;
mod types; mod types;
mod util; mod util;
@ -15,6 +17,8 @@ mod util;
pub const SEED: u32 = 0; pub const SEED: u32 = 0;
pub use dictionary::{Dictionary, Entry, HashGroup}; pub use dictionary::{Dictionary, Entry, HashGroup};
pub use idstring32::*;
pub use idstring64::*;
pub use murmurhash64::hash; pub use murmurhash64::hash;
pub use murmurhash64::hash32; pub use murmurhash64::hash32;
pub use murmurhash64::hash_inverse as inverse; pub use murmurhash64::hash_inverse as inverse;

View file

@ -119,4 +119,9 @@ fn test_hash() {
} }
#[test] #[test]
fn test_inverse() {} fn test_inverse() {
let h = hash("lua".as_bytes(), crate::murmur::SEED as u64);
let inv = hash_inverse(h, crate::murmur::SEED as u64);
assert_eq!(h, hash(&inv.to_le_bytes(), crate::murmur::SEED as u64));
assert_ne!(h, hash(&inv.to_be_bytes(), crate::murmur::SEED as u64));
}

View file

@ -150,6 +150,12 @@ impl fmt::UpperHex for Murmur32 {
} }
} }
impl fmt::LowerHex for Murmur32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
}
}
impl fmt::Display for Murmur32 { impl fmt::Display for Murmur32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:08X}", self) write!(f, "{:08X}", self)
@ -218,148 +224,3 @@ impl<'de> Deserialize<'de> for Murmur32 {
deserializer.deserialize_any(Self(0)) deserializer.deserialize_any(Self(0))
} }
} }
// This type encodes the fact that when reading in a bundle, we don't always have a dictionary
// entry for every hash in there. So we do want to have the real string available when needed,
// but at the same time retain the original hash information for when we don't.
// This is especially important when wanting to write back the read bundle, as the hashes need to
// stay the same.
// The previous system of always turning hashes into strings worked well for the purpose of
// displaying hashes, but would have made it very hard to turn a stringyfied hash back into
// an actual hash.
#[derive(Clone, Debug, Eq)]
pub enum IdString64 {
Hash(Murmur64),
String(String),
}
impl IdString64 {
pub fn to_murmur64(&self) -> Murmur64 {
match self {
Self::Hash(hash) => *hash,
Self::String(s) => Murmur64::hash(s.as_bytes()),
}
}
pub fn display(&self) -> IdString64Display {
let s = match self {
IdString64::Hash(hash) => hash.to_string(),
IdString64::String(s) => s.clone(),
};
IdString64Display(s)
}
pub fn is_string(&self) -> bool {
match self {
IdString64::Hash(_) => false,
IdString64::String(_) => true,
}
}
pub fn is_hash(&self) -> bool {
match self {
IdString64::Hash(_) => true,
IdString64::String(_) => false,
}
}
}
impl<S: Into<String>> From<S> for IdString64 {
fn from(value: S) -> Self {
Self::String(value.into())
}
}
impl From<Murmur64> for IdString64 {
fn from(value: Murmur64) -> Self {
Self::Hash(value)
}
}
impl From<IdString64> for Murmur64 {
fn from(value: IdString64) -> Self {
value.to_murmur64()
}
}
impl PartialEq for IdString64 {
fn eq(&self, other: &Self) -> bool {
self.to_murmur64() == other.to_murmur64()
}
}
impl std::hash::Hash for IdString64 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.to_murmur64().into());
}
}
impl serde::Serialize for IdString64 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(self.to_murmur64().into())
}
}
struct IdString64Visitor;
impl<'de> serde::de::Visitor<'de> for IdString64Visitor {
type Value = IdString64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an u64 or a string")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::Hash(value.into()))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::String(v.to_string()))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IdString64::String(v))
}
}
impl<'de> serde::Deserialize<'de> for IdString64 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u64(IdString64Visitor)
}
}
pub struct IdString64Display(String);
impl std::fmt::Display for IdString64Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::UpperHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::UpperHex::fmt(&self.to_murmur64(), f)
}
}
impl std::fmt::LowerHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::LowerHex::fmt(&self.to_murmur64(), f)
}
}

@ -1 +0,0 @@
Subproject commit 73d2b23ce50e75b184f5092ad515e97a0adbe6da