diff --git a/SteamFlow/Cargo.lock b/SteamFlow/Cargo.lock index 9f8d194e..da960756 100644 --- a/SteamFlow/Cargo.lock +++ b/SteamFlow/Cargo.lock @@ -20,15 +20,15 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" -version = "0.16.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" +checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99" [[package]] name = "accesskit_atspi_common" -version = "0.9.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5393c75d4666f580f4cac0a968bc97c36076bb536a129f28210dac54ee127ed" +checksum = "890d241cf51fc784f0ac5ac34dfc847421f8d39da6c7c91a0fcc987db62a8267" dependencies = [ "accesskit", "accesskit_consumer", @@ -40,33 +40,33 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.24.3" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a12dc159d52233c43d9fe5415969433cbdd52c3d6e0df51bda7d447427b9986" +checksum = "db81010a6895d8707f9072e6ce98070579b43b717193d2614014abd5cb17dd43" dependencies = [ "accesskit", - "immutable-chunkmap", + "hashbrown 0.15.5", ] [[package]] name = "accesskit_macos" -version = "0.17.4" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6" +checksum = "a0089e5c0ac0ca281e13ea374773898d9354cc28d15af9f0f7394d44a495b575" dependencies = [ "accesskit", "accesskit_consumer", + "hashbrown 0.15.5", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", - "once_cell", ] [[package]] name = "accesskit_unix" -version = "0.12.3" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7f5cf6165be10a54b2655fa2e0e12b2509f38ed6fc43e11c31fdb7ee6230bb" +checksum = "301e55b39cfc15d9c48943ce5f572204a551646700d0e8efa424585f94fec528" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -82,23 +82,23 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.23.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974e96c347384d9133427167fb8a58c340cb0496988dacceebdc1ed27071023b" +checksum = "d2d63dd5041e49c363d83f5419a896ecb074d309c414036f616dc0b04faca971" dependencies = [ "accesskit", "accesskit_consumer", - "paste", + "hashbrown 0.15.5", "static_assertions", - "windows 0.58.0", - "windows-core 0.58.0", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] name = "accesskit_winit" -version = "0.22.4" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea3522719f1c44564d03e9469a8e2f3a98b3a8a880bd66d0789c6b9c4a669dd" +checksum = "c8cfabe59d0eaca7412bfb1f70198dd31e3b0496fee7e15b066f9c36a1a140a0" dependencies = [ "accesskit", "accesskit_macos", @@ -163,7 +163,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "thiserror 1.0.69", ] @@ -207,9 +207,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", + "image", "log", "objc2 0.6.3", "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", @@ -288,17 +291,6 @@ dependencies = [ "slab", ] -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - [[package]] name = "async-io" version = "2.6.0" @@ -354,7 +346,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -394,7 +386,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -411,7 +403,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -422,9 +414,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atspi" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +checksum = "c83247582e7508838caf5f316c00791eee0e15c0bf743e6880585b867e16815c" dependencies = [ "atspi-common", "atspi-connection", @@ -433,9 +425,9 @@ dependencies = [ [[package]] name = "atspi-common" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +checksum = "33dfc05e7cdf90988a197803bf24f5788f94f7c94a69efa95683e8ffe76cfdfb" dependencies = [ "enumflags2", "serde", @@ -449,9 +441,9 @@ dependencies = [ [[package]] name = "atspi-connection" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" +checksum = "4193d51303d8332304056ae0004714256b46b6635a5c556109b319c0d3784938" dependencies = [ "atspi-common", "atspi-proxies", @@ -461,14 +453,13 @@ dependencies = [ [[package]] name = "atspi-proxies" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +checksum = "d2eebcb9e7e76f26d0bcfd6f0295e1cd1e6f33bedbc5698a971db8dc43d7751c" dependencies = [ "atspi-common", "serde", "zbus", - "zvariant", ] [[package]] @@ -532,23 +523,23 @@ dependencies = [ "owo-colors", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "bit-set" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -631,7 +622,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -736,12 +727,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -787,45 +772,15 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] -[[package]] -name = "com" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" -dependencies = [ - "com_macros", -] - -[[package]] -name = "com_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" -dependencies = [ - "com_macros_support", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "com_macros_support" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "combine" version = "4.6.7" @@ -885,7 +840,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -901,6 +856,17 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -940,6 +906,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -1065,7 +1037,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1106,9 +1078,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "ecolor" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e" dependencies = [ "bytemuck", "emath", @@ -1116,9 +1088,9 @@ dependencies = [ [[package]] name = "eframe" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac2645a9bf4826eb4e91488b1f17b8eaddeef09396706b2f14066461338e24f" +checksum = "457481173e6db5ca9fa2be93a58df8f4c7be639587aeb4853b526c6cf87db4e6" dependencies = [ "ahash", "bytemuck", @@ -1127,7 +1099,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow 0.14.2", + "glow", "glutin", "glutin-winit", "image", @@ -1138,36 +1110,40 @@ dependencies = [ "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", + "profiling", "raw-window-handle", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "web-time", - "winapi", - "windows-sys 0.52.0", + "windows-sys 0.61.2", "winit", ] [[package]] name = "egui" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3" dependencies = [ "accesskit", "ahash", + "bitflags 2.10.0", "emath", "epaint", "log", "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", ] [[package]] name = "egui-wgpu" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" +checksum = "5e4d209971c84b2352a06174abdba701af1e552ce56b144d96f2bd50a3c91236" dependencies = [ "ahash", "bytemuck", @@ -1175,7 +1151,8 @@ dependencies = [ "egui", "epaint", "log", - "thiserror 1.0.69", + "profiling", + "thiserror 2.0.18", "type-map", "web-time", "wgpu", @@ -1184,15 +1161,19 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" +checksum = "ec6687e5bb551702f4ad10ac428bab12acf9d53047ebb1082d4a0ed8c6251a29" dependencies = [ "accesskit_winit", - "ahash", "arboard", + "bytemuck", "egui", "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "profiling", "raw-window-handle", "smithay-clipboard", "web-time", @@ -1202,16 +1183,16 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e39bccc683cd43adab530d8f21a13eb91e80de10bcc38c3f1c16601b6f62b26" +checksum = "6420863ea1d90e750f75075231a260030ad8a9f30a7cef82cdc966492dc4c4eb" dependencies = [ - "ahash", "bytemuck", "egui", - "glow 0.14.2", + "glow", "log", "memoffset", + "profiling", "wasm-bindgen", "web-sys", "winit", @@ -1225,9 +1206,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" +checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32" dependencies = [ "bytemuck", ] @@ -1256,14 +1237,14 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "epaint" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62" dependencies = [ "ab_glyph", "ahash", @@ -1274,13 +1255,14 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", + "profiling", ] [[package]] name = "epaint_default_fonts" -version = "0.29.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" +checksum = "5c4fbe202b6578d3d56428fa185cdf114a05e49da05f477b3c7f0fbb221f1862" [[package]] name = "equivalent" @@ -1331,6 +1313,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1362,6 +1364,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1380,7 +1388,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1447,7 +1455,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1496,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.3", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1552,21 +1560,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glow" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -1581,7 +1577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ "bitflags 2.10.0", - "cfg_aliases 0.2.1", + "cfg_aliases", "cgl", "dispatch2", "glutin_egl_sys", @@ -1605,7 +1601,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ - "cfg_aliases 0.2.1", + "cfg_aliases", "glutin", "raw-window-handle", "winit", @@ -1661,15 +1657,14 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", "thiserror 1.0.69", - "winapi", - "windows 0.52.0", + "windows 0.58.0", ] [[package]] @@ -1692,6 +1687,18 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1704,7 +1711,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1712,20 +1719,8 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hassle-rs" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.10.0", - "com", - "libc", - "libloading", - "thiserror 1.0.69", - "widestring", - "winapi", + "foldhash 0.2.0", ] [[package]] @@ -1980,17 +1975,9 @@ dependencies = [ "moxcms", "num-traits", "png", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "immutable-chunkmap" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" -dependencies = [ - "arrayvec", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.12", ] [[package]] @@ -2153,7 +2140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2273,13 +2260,13 @@ dependencies = [ [[package]] name = "metal" -version = "0.29.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.10.0", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "log", "objc", @@ -2319,23 +2306,28 @@ dependencies = [ [[package]] name = "naga" -version = "22.1.0" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" dependencies = [ "arrayvec", "bit-set", "bitflags 2.10.0", - "cfg_aliases 0.1.1", + "cfg-if", + "cfg_aliases", "codespan-reporting", + "half", + "hashbrown 0.16.1", "hexf-parse", "indexmap", + "libm", "log", + "num-traits", + "once_cell", "rustc-hash 1.1.0", "spirv", - "termcolor", - "thiserror 1.0.69", - "unicode-xid", + "thiserror 2.0.18", + "unicode-ident", ] [[package]] @@ -2347,7 +2339,7 @@ dependencies = [ "bitflags 2.10.0", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -2359,15 +2351,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -2377,19 +2360,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset", -] - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2471,7 +2441,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -2776,6 +2746,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2827,7 +2806,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2878,7 +2857,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -2958,6 +2937,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -2989,7 +2983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn", ] [[package]] @@ -3036,7 +3030,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -3070,14 +3064,10 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.30.0" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" @@ -3086,6 +3076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", + "serde", ] [[package]] @@ -3095,7 +3086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", - "cfg_aliases 0.2.1", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -3135,7 +3126,7 @@ version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "once_cell", "socket2", @@ -3217,6 +3208,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3524,7 +3521,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -3548,7 +3545,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -3897,17 +3894,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.114" @@ -3936,7 +3922,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -3987,7 +3973,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -3998,7 +3984,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -4010,6 +3996,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -4083,7 +4083,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -4232,7 +4232,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -4391,6 +4391,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -4492,7 +4503,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "wasm-bindgen-shared", ] @@ -4658,7 +4669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", - "quick-xml 0.38.4", + "quick-xml", "quote", ] @@ -4728,18 +4739,29 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wgpu" -version = "22.1.0" +version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", - "cfg_aliases 0.1.1", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", "document-features", + "hashbrown 0.16.1", "js-sys", "log", + "naga", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "smallvec", @@ -4754,47 +4776,85 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "22.1.0" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" dependencies = [ "arrayvec", + "bit-set", "bit-vec", "bitflags 2.10.0", - "cfg_aliases 0.1.1", + "bytemuck", + "cfg_aliases", "document-features", + "hashbrown 0.16.1", "indexmap", "log", "naga", "once_cell", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-hal" -version = "22.0.0" +version = "27.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" dependencies = [ "android_system_properties", "arrayvec", "ash", + "bit-set", "bitflags 2.10.0", - "cfg_aliases 0.1.1", - "core-graphics-types", - "glow 0.13.1", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hassle-rs", + "hashbrown 0.16.1", "js-sys", "khronos-egl", "libc", @@ -4802,39 +4862,40 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys", "objc", "once_cell", + "ordered-float", "parking_lot", + "portable-atomic", + "portable-atomic-util", "profiling", + "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "web-sys", "wgpu-types", - "winapi", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "22.0.0" +version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" dependencies = [ "bitflags 2.10.0", + "bytemuck", "js-sys", + "log", + "thiserror 2.0.18", "web-sys", ] -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "winapi" version = "0.3.9" @@ -4868,31 +4929,34 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.52.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core 0.52.0", + "windows-core 0.58.0", "windows-targets 0.52.6", ] [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.61.2", ] [[package]] @@ -4901,13 +4965,37 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -4916,7 +5004,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -4927,15 +5026,42 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -4945,16 +5071,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -5006,7 +5150,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5061,7 +5205,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5072,6 +5216,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5265,7 +5418,7 @@ dependencies = [ "block2", "bytemuck", "calloop 0.13.0", - "cfg_aliases 0.2.1", + "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", "core-graphics", @@ -5343,7 +5496,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5359,7 +5512,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5445,16 +5598,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -5508,19 +5651,18 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", "synstructure", ] [[package]] name = "zbus" -version = "4.4.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" dependencies = [ "async-broadcast", "async-executor", - "async-fs", "async-io", "async-lock", "async-process", @@ -5531,20 +5673,18 @@ dependencies = [ "enumflags2", "event-listener", "futures-core", - "futures-sink", - "futures-util", + "futures-lite", "hex", - "nix", + "libc", "ordered-stream", - "rand 0.8.5", + "rustix 1.1.3", "serde", "serde_repr", - "sha1", - "static_assertions", "tracing", "uds_windows", - "windows-sys 0.52.0", - "xdg-home", + "uuid", + "windows-sys 0.61.2", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -5552,9 +5692,9 @@ dependencies = [ [[package]] name = "zbus-lockstep" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" dependencies = [ "zbus_xml", "zvariant", @@ -5562,13 +5702,13 @@ dependencies = [ [[package]] name = "zbus-lockstep-macros" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", "zbus-lockstep", "zbus_xml", "zvariant", @@ -5576,37 +5716,38 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "4.4.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn", + "zbus_names", + "zvariant", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "3.0.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", + "winnow", "zvariant", ] [[package]] name = "zbus_xml" -version = "4.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac" dependencies = [ - "quick-xml 0.30.0", + "quick-xml", "serde", - "static_assertions", "zbus_names", "zvariant", ] @@ -5628,7 +5769,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -5648,7 +5789,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", "synstructure", ] @@ -5688,7 +5829,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -5697,54 +5838,72 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" dependencies = [ - "zune-core", + "zune-core 0.5.1", ] [[package]] name = "zvariant" -version = "4.2.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" dependencies = [ "endi", "enumflags2", "serde", - "static_assertions", + "winnow", "zvariant_derive", + "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "4.2.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "2.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "serde", + "syn", + "winnow", ] diff --git a/SteamFlow/Cargo.toml b/SteamFlow/Cargo.toml index 3fc8ed9d..5097363b 100644 --- a/SteamFlow/Cargo.toml +++ b/SteamFlow/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT" [dependencies] anyhow = "1" directories = "5" -eframe = { version = "0.29", default-features = true } -egui = "0.29" +eframe = { version = "0.33", default-features = true } +egui = "0.33" image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } keyvalues-serde = "0.2" prost = "0.13" diff --git a/SteamFlow/src/depot_browser.rs b/SteamFlow/src/depot_browser.rs new file mode 100644 index 00000000..6c741fc9 --- /dev/null +++ b/SteamFlow/src/depot_browser.rs @@ -0,0 +1,226 @@ +use anyhow::{anyhow, bail, Context, Result}; +use serde::Deserialize; +use sha1::Digest; +use std::collections::HashMap; +use std::path::Path; + +use steam_vent::connection::Connection; +use steam_vent::proto::content_manifest::ContentManifestPayload; +use steam_vent::proto::steammessages_clientserver_appinfo::{ + cmsg_client_picsproduct_info_request, CMsgClientPICSProductInfoRequest, + CMsgClientPICSProductInfoResponse, +}; +use steam_vent::ConnectionTrait; + +use crate::download_pipeline::{ + phase2_get_security_info, phase3_download_manifest, DownloadState, ManifestSelection, +}; + +#[derive(Debug, Clone)] +pub struct DepotInfo { + pub depot_id: u32, + pub name: String, + pub max_size: u64, + pub public_manifest_id: Option, +} + +#[derive(Debug, Clone)] +pub struct ManifestFileEntry { + pub filename: String, + pub size: u64, + pub sha_hash: String, + pub chunks: usize, +} + +#[derive(Debug, Deserialize)] +struct AppInfoRoot { + #[serde(default)] + appinfo: Option, + #[serde(default)] + depots: HashMap, +} + +#[derive(Debug, Deserialize)] +struct AppInfoNode { + #[serde(default)] + depots: HashMap, +} + +#[derive(Debug, Deserialize)] +struct DepotNode { + #[serde(default)] + name: Option, + #[serde(default)] + maxsize: Option, + #[serde(default)] + manifests: Option, +} + +#[derive(Debug, Deserialize)] +struct DepotManifests { + #[serde(default)] + public: Option, +} + +pub async fn fetch_depots(connection: &Connection, appid: u32) -> Result> { + let parsed = fetch_appinfo(connection, appid).await?; + let depots = parsed.appinfo.map(|n| n.depots).unwrap_or(parsed.depots); + + let mut out = Vec::new(); + for (depot_id, node) in depots { + if !depot_id.chars().all(|c| c.is_ascii_digit()) { + continue; + } + let depot_id = depot_id.parse::()?; + let max_size = node + .maxsize + .as_deref() + .and_then(|v| v.parse::().ok()) + .unwrap_or_default(); + let public_manifest_id = node + .manifests + .and_then(|m| m.public) + .and_then(|v| v.parse::().ok()); + + out.push(DepotInfo { + depot_id, + name: node.name.unwrap_or_else(|| format!("Depot {depot_id}")), + max_size, + public_manifest_id, + }); + } + + out.sort_by_key(|d| d.depot_id); + Ok(out) +} + +pub async fn fetch_manifest_files( + connection: &Connection, + appid: u32, + depot_id: u32, + manifest_ref: &str, +) -> Result> { + let manifest_id = resolve_manifest_id(connection, appid, depot_id, manifest_ref).await?; + let security = phase2_get_security_info(connection, appid, depot_id).await?; + let selection = ManifestSelection { + app_id: appid, + depot_id, + manifest_id, + appinfo_vdf: String::new(), + }; + + let manifest = phase3_download_manifest(&selection, &security).await?; + let mut files = Vec::new(); + for file in &manifest.mappings { + let filename = file.filename().to_string(); + if filename.is_empty() { + continue; + } + + let mut hasher = sha1::Sha1::new(); + for chunk in &file.chunks { + hasher.update(chunk.sha()); + } + let sha_hash = hex::encode(hasher.finalize()); + + files.push(ManifestFileEntry { + filename, + size: file.size(), + sha_hash, + chunks: file.chunks.len(), + }); + } + + files.sort_by(|a, b| a.filename.cmp(&b.filename)); + Ok(files) +} + +pub fn download_single_file( + connection: &Connection, + appid: u32, + depot_id: u32, + manifest_ref: &str, + file_path: &str, + output_dir: &Path, +) -> Result<()> { + let runtime = tokio::runtime::Runtime::new()?; + let manifest_id = runtime.block_on(resolve_manifest_id( + connection, + appid, + depot_id, + manifest_ref, + ))?; + let security = runtime.block_on(phase2_get_security_info(connection, appid, depot_id))?; + let selection = ManifestSelection { + app_id: appid, + depot_id, + manifest_id, + appinfo_vdf: String::new(), + }; + let manifest = runtime.block_on(phase3_download_manifest(&selection, &security))?; + + let file_entry = manifest + .mappings + .iter() + .find(|entry| entry.filename() == file_path) + .cloned() + .ok_or_else(|| anyhow!("file not found in manifest: {file_path}"))?; + + std::fs::create_dir_all(output_dir) + .with_context(|| format!("failed creating {}", output_dir.display()))?; + + let state = DownloadState::new( + ContentManifestPayload::default(), + output_dir.to_path_buf(), + security, + None, + false, + ); + runtime.block_on(state.download_file(file_entry)) +} + +async fn resolve_manifest_id( + connection: &Connection, + appid: u32, + depot_id: u32, + manifest_ref: &str, +) -> Result { + if let Ok(id) = manifest_ref.trim().parse::() { + return Ok(id); + } + + if manifest_ref.trim().is_empty() || manifest_ref.eq_ignore_ascii_case("public") { + let depots = fetch_depots(connection, appid).await?; + return depots + .into_iter() + .find(|d| d.depot_id == depot_id) + .and_then(|d| d.public_manifest_id) + .ok_or_else(|| anyhow!("missing public manifest for depot {depot_id}")); + } + + bail!("unsupported manifest reference '{manifest_ref}', expected numeric id or 'public'") +} + +async fn fetch_appinfo(connection: &Connection, appid: u32) -> Result { + let mut request = CMsgClientPICSProductInfoRequest::new(); + request + .apps + .push(cmsg_client_picsproduct_info_request::AppInfo { + appid: Some(appid), + ..Default::default() + }); + + let response: CMsgClientPICSProductInfoResponse = connection + .job(request) + .await + .context("failed requesting appinfo product info")?; + + let app = response + .apps + .iter() + .find(|entry| entry.appid() == appid) + .ok_or_else(|| anyhow!("missing appinfo payload for app {appid}"))?; + + let raw_vdf = String::from_utf8_lossy(app.buffer()).to_string(); + keyvalues_serde::from_str(&raw_vdf).context("failed parsing appinfo VDF") +} diff --git a/SteamFlow/src/download_pipeline.rs b/SteamFlow/src/download_pipeline.rs index 3968f3b4..2e8ff5f4 100644 --- a/SteamFlow/src/download_pipeline.rs +++ b/SteamFlow/src/download_pipeline.rs @@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Context, Result}; use cbc::cipher::block_padding::Pkcs7; use cbc::cipher::{BlockDecryptMut, KeyIvInit}; use flate2::read::GzDecoder; +use sha1::Digest; use std::collections::HashMap; use std::io::{Cursor, Read, SeekFrom}; use std::path::{Path, PathBuf}; @@ -25,7 +26,7 @@ use steam_vent::proto::steammessages_contentsystem_steamclient::{ }; use steam_vent::ConnectionTrait; use tokio::fs::OpenOptions; -use tokio::io::{AsyncSeekExt, AsyncWriteExt}; +use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use crate::install::ProgressEvent; use xz2::read::XzDecoder; @@ -270,6 +271,7 @@ pub struct DownloadState { pub client: reqwest::Client, pub security: SecurityInfo, pub progress_tx: Option>, + pub smart_verify_existing: bool, } impl DownloadState { @@ -278,6 +280,7 @@ impl DownloadState { install_dir: PathBuf, security: SecurityInfo, progress_tx: Option>, + smart_verify_existing: bool, ) -> Self { Self { manifest, @@ -285,6 +288,7 @@ impl DownloadState { client: reqwest::Client::new(), security, progress_tx, + smart_verify_existing, } } @@ -310,9 +314,19 @@ impl DownloadState { .with_context(|| format!("failed creating {}", parent.display()))?; } + let file_exists = file_path.exists(); + let existing_len = if file_exists { + std::fs::metadata(&file_path) + .map(|meta| meta.len()) + .unwrap_or_default() + } else { + 0 + }; + let mut output = OpenOptions::new() .create(true) - .truncate(true) + .truncate(!self.smart_verify_existing) + .read(self.smart_verify_existing) .write(true) .open(&file_path) .await @@ -326,7 +340,42 @@ impl DownloadState { let mut bytes_downloaded = 0_u64; - for chunk in &file_entry.chunks { + for (index, chunk) in file_entry.chunks.iter().enumerate() { + let chunk_offset = chunk.offset(); + let next_offset = file_entry + .chunks + .get(index + 1) + .map(|next| next.offset()) + .unwrap_or(total_bytes); + let expected_chunk_len = next_offset.saturating_sub(chunk_offset) as usize; + + if self.smart_verify_existing + && file_exists + && expected_chunk_len > 0 + && chunk_offset + expected_chunk_len as u64 <= existing_len + { + let mut existing_chunk = vec![0_u8; expected_chunk_len]; + output + .seek(SeekFrom::Start(chunk_offset)) + .await + .with_context(|| format!("failed seeking {}", file_path.display()))?; + if output.read_exact(&mut existing_chunk).await.is_ok() { + let digest = sha1::Sha1::digest(&existing_chunk); + if digest.as_slice() == chunk.sha() { + bytes_downloaded = + (bytes_downloaded + expected_chunk_len as u64).min(total_bytes); + if let Some(tx) = &self.progress_tx { + let _ = tx.send(ProgressEvent { + file_name: filename.clone(), + bytes_downloaded, + total_bytes, + }); + } + continue; + } + } + } + let chunk_id = hex::encode(chunk.sha()); let url = format!( "https://{}/depot/{}/chunk/{}", @@ -355,7 +404,7 @@ impl DownloadState { let processed = process_chunk(chunk_data, &self.security.depot_key); output - .seek(SeekFrom::Start(chunk.offset())) + .seek(SeekFrom::Start(chunk_offset)) .await .with_context(|| format!("failed seeking {}", file_path.display()))?; output @@ -391,6 +440,7 @@ pub fn phase4_download_chunks( manifest: &ContentManifestPayload, security: &SecurityInfo, install_root: &Path, + smart_verify_existing: bool, ) -> Result<()> { std::fs::create_dir_all(install_root) .with_context(|| format!("failed creating install root {}", install_root.display()))?; @@ -401,6 +451,7 @@ pub fn phase4_download_chunks( install_root.to_path_buf(), security.clone(), None, + smart_verify_existing, ); runtime.block_on(state.download_all_files()) } @@ -425,7 +476,29 @@ pub fn execute_four_step_download( ))?; let manifest = runtime.block_on(phase3_download_manifest(&selection, &security))?; - phase4_download_chunks(&manifest, &security, install_root)?; + phase4_download_chunks(&manifest, &security, install_root, false)?; + Ok(()) +} + +pub fn execute_download_with_manifest_id( + connection: &Connection, + app_id: u32, + depot_id: u32, + manifest_id: u64, + install_root: &Path, + smart_verify_existing: bool, +) -> Result<()> { + let runtime = tokio::runtime::Runtime::new()?; + + let security = runtime.block_on(phase2_get_security_info(connection, app_id, depot_id))?; + let selection = ManifestSelection { + app_id, + depot_id, + manifest_id, + appinfo_vdf: String::new(), + }; + let manifest = runtime.block_on(phase3_download_manifest(&selection, &security))?; + phase4_download_chunks(&manifest, &security, install_root, smart_verify_existing)?; Ok(()) } diff --git a/SteamFlow/src/lib.rs b/SteamFlow/src/lib.rs index 5defc653..8239a664 100644 --- a/SteamFlow/src/lib.rs +++ b/SteamFlow/src/lib.rs @@ -1,6 +1,7 @@ pub mod cloud_sync; pub mod cm_list; pub mod config; +pub mod depot_browser; pub mod download_pipeline; pub mod install; pub mod library; diff --git a/SteamFlow/src/steam_client.rs b/SteamFlow/src/steam_client.rs index 0e14e693..84f6a5bb 100644 --- a/SteamFlow/src/steam_client.rs +++ b/SteamFlow/src/steam_client.rs @@ -4,6 +4,7 @@ use crate::config::{ library_cache_path, load_launcher_config, load_library_cache, load_session, save_library_cache, save_session, }; +use crate::depot_browser::{self, DepotInfo, ManifestFileEntry}; use crate::download_pipeline::{self, DepotDownloadPlan, DepotPlatform}; use crate::models::{ DownloadProgress, DownloadProgressState, LibraryGame, OwnedGame, SessionState, SteamGuardReq, @@ -381,6 +382,85 @@ impl SteamClient { Ok(rx) } + pub fn uninstall_game(&self, appid: u32, delete_prefix: bool) -> Result<()> { + let cfg = tokio::runtime::Handle::current().block_on(load_launcher_config())?; + let steamapps = PathBuf::from(cfg.steam_library_path).join("steamapps"); + let appmanifest = steamapps.join(format!("appmanifest_{appid}.acf")); + + let install_dir = if appmanifest.exists() { + let raw = std::fs::read_to_string(&appmanifest) + .with_context(|| format!("failed reading {}", appmanifest.display()))?; + parse_installdir_from_acf(&raw) + .map(|dir| steamapps.join("common").join(dir)) + .unwrap_or_else(|| steamapps.join("common").join(appid.to_string())) + } else { + steamapps.join("common").join(appid.to_string()) + }; + + if install_dir.exists() { + std::fs::remove_dir_all(&install_dir) + .with_context(|| format!("failed deleting {}", install_dir.display()))?; + } + + if appmanifest.exists() { + std::fs::remove_file(&appmanifest) + .with_context(|| format!("failed deleting {}", appmanifest.display()))?; + } + + if delete_prefix { + let compat = steamapps.join("compatdata").join(appid.to_string()); + if compat.exists() { + std::fs::remove_dir_all(&compat) + .with_context(|| format!("failed deleting {}", compat.display()))?; + } + } + + Ok(()) + } + + pub async fn fetch_depots(&self, appid: u32) -> Result> { + let connection = self + .connection + .as_ref() + .context("steam connection not initialized")?; + depot_browser::fetch_depots(connection, appid).await + } + + pub async fn fetch_manifest_files( + &self, + appid: u32, + depot_id: u32, + manifest_ref: &str, + ) -> Result> { + let connection = self + .connection + .as_ref() + .context("steam connection not initialized")?; + depot_browser::fetch_manifest_files(connection, appid, depot_id, manifest_ref).await + } + + pub fn download_single_file( + &self, + appid: u32, + depot_id: u32, + manifest_ref: &str, + file_path: &str, + output_dir: &Path, + ) -> Result<()> { + let connection = self + .connection + .as_ref() + .context("steam connection not initialized")?; + depot_browser::download_single_file( + connection, + appid, + depot_id, + manifest_ref, + file_path, + output_dir, + ) + } + pub async fn fetch_owned_games(&mut self) -> Result> { let connection = self .connection @@ -629,6 +709,209 @@ impl SteamClient { Ok(()) } + pub fn update_game(&self, appid: u32) -> Result> { + self.start_manifest_download(appid, false) + } + + pub fn verify_game(&self, appid: u32) -> Result> { + self.start_manifest_download(appid, true) + } + + fn start_manifest_download( + &self, + appid: u32, + smart_verify_existing: bool, + ) -> Result> { + let connection = self + .connection + .as_ref() + .cloned() + .context("steam connection not initialized")?; + + let install_root = self.install_root_for_app(appid)?; + let manifest_path = self.appmanifest_path(appid)?; + let (tx, rx) = tokio::sync::mpsc::channel(128); + + let local_manifests = self.local_manifest_ids_for_appid(appid).unwrap_or_default(); + + tokio::task::spawn(async move { + let _ = tx + .send(DownloadProgress { + state: DownloadProgressState::Queued, + bytes_downloaded: 0, + total_bytes: 0, + current_file: if smart_verify_existing { + "verifying installed chunks".to_string() + } else { + "resolving latest manifest".to_string() + }, + }) + .await; + + let remote_manifests = if smart_verify_existing { + local_manifests.clone() + } else { + SteamClient::remote_manifest_ids_static(&connection, appid) + .await + .unwrap_or_default() + }; + + let selected = if smart_verify_existing { + local_manifests.iter().next().map(|(d, m)| (*d as u32, *m)) + } else { + remote_manifests.iter().next().map(|(d, m)| (*d as u32, *m)) + }; + + let Some((depot_id, manifest_id)) = selected else { + let _ = tx + .send(DownloadProgress { + state: DownloadProgressState::Failed, + bytes_downloaded: 0, + total_bytes: 0, + current_file: "no manifest/depot available for download".to_string(), + }) + .await; + return; + }; + + let result = tokio::task::spawn_blocking(move || { + download_pipeline::execute_download_with_manifest_id( + &connection, + appid, + depot_id, + manifest_id, + &install_root, + smart_verify_existing, + ) + }) + .await; + + match result { + Ok(Ok(())) => { + if !smart_verify_existing { + let _ = SteamClient::write_manifest_ids_at_path( + &manifest_path, + &remote_manifests, + ); + } + let _ = tx + .send(DownloadProgress { + state: DownloadProgressState::Completed, + bytes_downloaded: 1, + total_bytes: 1, + current_file: if smart_verify_existing { + "verify completed".to_string() + } else { + "update completed".to_string() + }, + }) + .await; + } + Ok(Err(err)) => { + let _ = tx + .send(DownloadProgress { + state: DownloadProgressState::Failed, + bytes_downloaded: 0, + total_bytes: 0, + current_file: err.to_string(), + }) + .await; + } + Err(err) => { + let _ = tx + .send(DownloadProgress { + state: DownloadProgressState::Failed, + bytes_downloaded: 0, + total_bytes: 0, + current_file: format!("download task join failure: {err}"), + }) + .await; + } + } + }); + + Ok(rx) + } + + fn appmanifest_path(&self, appid: u32) -> Result { + let cfg = tokio::runtime::Handle::current().block_on(load_launcher_config())?; + Ok(PathBuf::from(cfg.steam_library_path) + .join("steamapps") + .join(format!("appmanifest_{appid}.acf"))) + } + + fn local_manifest_ids_for_appid(&self, appid: u32) -> Result> { + let manifest_path = self.appmanifest_path(appid)?; + if !manifest_path.exists() { + return Ok(HashMap::new()); + } + let raw = std::fs::read_to_string(&manifest_path) + .with_context(|| format!("failed reading {}", manifest_path.display()))?; + Ok(parse_installed_depots_from_acf(&raw)) + } + + fn install_root_for_app(&self, appid: u32) -> Result { + let manifest_path = self.appmanifest_path(appid)?; + let steamapps = manifest_path + .parent() + .ok_or_else(|| anyhow!("invalid steamapps path for app {appid}"))? + .to_path_buf(); + + if manifest_path.exists() { + let raw = std::fs::read_to_string(&manifest_path) + .with_context(|| format!("failed reading {}", manifest_path.display()))?; + if let Some(installdir) = parse_installdir_from_acf(&raw) { + return Ok(steamapps.join("common").join(installdir)); + } + } + + Ok(PathBuf::from( + tokio::runtime::Handle::current() + .block_on(load_launcher_config())? + .steam_library_path, + ) + .join(appid.to_string())) + } + + async fn remote_manifest_ids_static( + connection: &Connection, + appid: u32, + ) -> Result> { + let mut request = CMsgClientPICSProductInfoRequest::new(); + request + .apps + .push(cmsg_client_picsproduct_info_request::AppInfo { + appid: Some(appid), + ..Default::default() + }); + + let response: CMsgClientPICSProductInfoResponse = connection + .job(request) + .await + .context("failed requesting appinfo product info for update metadata")?; + + let app = response + .apps + .iter() + .find(|entry| entry.appid() == appid) + .ok_or_else(|| anyhow!("missing appinfo payload for app {appid}"))?; + + let raw_vdf = String::from_utf8_lossy(app.buffer()).to_string(); + Ok(parse_remote_depot_manifests_from_vdf(&raw_vdf)) + } + + fn write_manifest_ids_at_path(path: &Path, new_manifest_ids: &HashMap) -> Result<()> { + if !path.exists() || new_manifest_ids.is_empty() { + return Ok(()); + } + let raw = std::fs::read_to_string(path) + .with_context(|| format!("failed reading {}", path.display()))?; + let rewritten = rewrite_installed_manifest_ids(&raw, new_manifest_ids); + std::fs::write(path, rewritten) + .with_context(|| format!("failed writing {}", path.display()))?; + Ok(()) + } + fn spawn_game_process( &self, app: &LibraryGame, @@ -784,6 +1067,69 @@ struct ProductLaunchEntry { oslist: Option, } +fn parse_installdir_from_acf(raw: &str) -> Option { + for line in raw.lines() { + let quoted = extract_quoted_values(line.trim()); + if quoted.len() >= 2 && quoted[0] == "installdir" { + return Some(quoted[1].clone()); + } + } + None +} + +fn rewrite_installed_manifest_ids(raw: &str, updated: &HashMap) -> String { + let mut out = Vec::new(); + let mut in_installed_depots = false; + let mut current_depot: Option = None; + + for line in raw.lines() { + let trimmed = line.trim(); + + if trimmed.contains("\"InstalledDepots\"") { + in_installed_depots = true; + out.push(line.to_string()); + continue; + } + + if in_installed_depots && trimmed == "}" { + if current_depot.is_some() { + current_depot = None; + out.push(line.to_string()); + continue; + } + in_installed_depots = false; + out.push(line.to_string()); + continue; + } + + if in_installed_depots { + let quoted = extract_quoted_values(trimmed); + if quoted.len() == 1 { + current_depot = quoted[0].parse::().ok(); + out.push(line.to_string()); + continue; + } + + if quoted.len() >= 2 && quoted[0] == "manifest" { + if let Some(depot_id) = current_depot { + if let Some(new_manifest) = updated.get(&depot_id) { + let indent = line + .chars() + .take_while(|ch| ch.is_whitespace()) + .collect::(); + out.push(format!(r#"{indent}"manifest" "{new_manifest}""#)); + continue; + } + } + } + } + + out.push(line.to_string()); + } + + format!("{}\n", out.join("\n")) +} + fn parse_installed_depots_from_acf(raw: &str) -> HashMap { let mut manifests = HashMap::new(); let mut in_installed_depots = false; diff --git a/SteamFlow/src/ui.rs b/SteamFlow/src/ui.rs index ff0a1ae1..008103d9 100644 --- a/SteamFlow/src/ui.rs +++ b/SteamFlow/src/ui.rs @@ -1,6 +1,7 @@ use crate::config::{ load_launcher_config, opensteam_image_cache_dir, save_launcher_config, LauncherConfig, }; +use crate::depot_browser::{DepotInfo, ManifestFileEntry}; use crate::install::{InstallPipeline, InstallStage, ProgressEvent}; use crate::library::{build_game_library, scan_installed_app_paths}; use crate::models::{ @@ -22,6 +23,23 @@ enum ProtonSource { Custom, } +#[derive(Debug, Clone)] +struct UninstallModalState { + app_id: u32, + game_name: String, + delete_prefix: bool, +} + +#[derive(Debug, Clone)] +struct DepotBrowserState { + app_id: u32, + game_name: String, + depots: Vec, + selected_depot: Option, + manifest_input: String, + files: Vec, +} + pub struct SteamLauncher { runtime: Runtime, pub client: SteamClient, @@ -32,6 +50,7 @@ pub struct SteamLauncher { image_rx: Receiver<(AppId, String)>, selected_app: Option, show_installed_only: bool, + search_text: String, proton_path_for_windows: String, status: String, auth_username: String, @@ -51,6 +70,8 @@ pub struct SteamLauncher { steam_protons: Vec, custom_protons: Vec, user_profile: Option, + uninstall_modal: Option, + depot_browser: Option, } impl SteamLauncher { @@ -72,6 +93,7 @@ impl SteamLauncher { image_rx, selected_app: None, show_installed_only: false, + search_text: String::new(), proton_path_for_windows: String::new(), status: if authenticated { "Ready".to_string() @@ -95,6 +117,8 @@ impl SteamLauncher { steam_protons, custom_protons, user_profile, + uninstall_modal: None, + depot_browser: None, } } @@ -102,6 +126,12 @@ impl SteamLauncher { self.library .iter() .filter(|g| !self.show_installed_only || g.is_installed) + .filter(|g| { + self.search_text.trim().is_empty() + || g.name + .to_ascii_lowercase() + .contains(&self.search_text.to_ascii_lowercase()) + }) .collect() } @@ -148,14 +178,33 @@ impl SteamLauncher { let path = cache_dir.join(format!("{appid}_library_600x900.jpg")); if tokio::fs::metadata(&path).await.is_err() { - let url = format!( - "https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900.jpg" - ); - - if let Ok(response) = reqwest::get(url).await { - if response.status().is_success() { - if let Ok(bytes) = response.bytes().await { - let _ = tokio::fs::write(&path, bytes).await; + let candidates = [ + ( + format!( + "https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900.jpg" + ), + path.clone(), + ), + ( + format!("https://steamcdn-a.akamaihd.net/steam/apps/{appid}/header.jpg"), + cache_dir.join(format!("{appid}_header.jpg")), + ), + ( + format!("https://steamcdn-a.akamaihd.net/steam/apps/{appid}/portrait.png"), + cache_dir.join(format!("{appid}_portrait.png")), + ), + ]; + + for (url, target_path) in candidates { + if let Ok(response) = reqwest::get(url).await { + if response.status().is_success() { + if let Ok(bytes) = response.bytes().await { + if tokio::fs::write(&target_path, bytes).await.is_ok() { + let _ = + tx.send((appid, target_path.to_string_lossy().to_string())); + return; + } + } } } } @@ -403,6 +452,217 @@ impl SteamLauncher { }); } + fn open_uninstall_modal(&mut self, game: &LibraryGame) { + self.uninstall_modal = Some(UninstallModalState { + app_id: game.app_id, + game_name: game.name.clone(), + delete_prefix: false, + }); + } + + fn open_depot_browser(&mut self, game: &LibraryGame) { + let depots = self.runtime.block_on(self.client.fetch_depots(game.app_id)); + match depots { + Ok(depots) => { + let selected_depot = depots.first().map(|d| d.depot_id); + let manifest_input = depots + .first() + .and_then(|d| d.public_manifest_id) + .map(|v| v.to_string()) + .unwrap_or_else(|| "public".to_string()); + self.depot_browser = Some(DepotBrowserState { + app_id: game.app_id, + game_name: game.name.clone(), + depots, + selected_depot, + manifest_input, + files: Vec::new(), + }); + } + Err(err) => { + self.status = format!("Failed to load depots for {}: {err}", game.name); + } + } + } + + fn draw_uninstall_modal(&mut self, ctx: &egui::Context) { + let mut do_uninstall = None; + let mut close = false; + if let Some(modal) = &mut self.uninstall_modal { + egui::Window::new(format!("Uninstall {}?", modal.game_name)) + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + ui.label(format!("Uninstall {}?", modal.game_name)); + ui.checkbox( + &mut modal.delete_prefix, + "Also delete Compatibility Data (Saves & Prefixes)", + ) + .on_hover_text( + "Check this to perform a clean wipe. Uncheck to keep saves for later.", + ); + ui.small( + "Check this to perform a clean wipe. Uncheck to keep saves for later.", + ); + ui.small("Steam userdata/cloud local cache is intentionally preserved."); + + ui.horizontal(|ui| { + if ui + .add( + egui::Button::new( + egui::RichText::new("Uninstall") + .color(egui::Color32::WHITE) + .strong(), + ) + .fill(egui::Color32::from_rgb(200, 45, 45)), + ) + .clicked() + { + do_uninstall = + Some((modal.app_id, modal.game_name.clone(), modal.delete_prefix)); + } + + if ui.button("Cancel").clicked() { + close = true; + } + }); + }); + } + + if let Some((app_id, game_name, delete_prefix)) = do_uninstall { + match self.client.uninstall_game(app_id, delete_prefix) { + Ok(()) => { + if let Some(game) = self.library.iter_mut().find(|g| g.app_id == app_id) { + game.is_installed = false; + game.install_path = None; + game.update_available = false; + game.local_manifest_ids.clear(); + } + self.status = format!("Uninstalled {game_name}"); + } + Err(err) => { + self.status = format!("Failed to uninstall {game_name}: {err}"); + } + } + self.uninstall_modal = None; + } else if close { + self.uninstall_modal = None; + } + } + + fn draw_depot_browser_window(&mut self, ctx: &egui::Context) { + let mut close = false; + let mut request_refresh: Option<(u32, u32, String)> = None; + let mut request_download: Option<(u32, u32, String, String)> = None; + + if let Some(state) = &mut self.depot_browser { + egui::Window::new(format!( + "Depot Browser - {} ({})", + state.game_name, state.app_id + )) + .resizable(true) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Manifest ID:"); + ui.add( + egui::TextEdit::singleline(&mut state.manifest_input) + .hint_text("public or numeric manifest id"), + ); + if ui.button("Load Manifest").clicked() { + if let Some(depot_id) = state.selected_depot { + request_refresh = + Some((state.app_id, depot_id, state.manifest_input.clone())); + } + } + }); + + ui.separator(); + ui.columns(2, |columns| { + columns[0].heading("Depots"); + egui::ScrollArea::vertical().show(&mut columns[0], |ui| { + for depot in &state.depots { + let label = format!( + "{} - {} ({} bytes)", + depot.depot_id, depot.name, depot.max_size + ); + if ui + .selectable_label( + state.selected_depot == Some(depot.depot_id), + label, + ) + .clicked() + { + state.selected_depot = Some(depot.depot_id); + if let Some(public_id) = depot.public_manifest_id { + state.manifest_input = public_id.to_string(); + } + } + } + }); + + columns[1].heading("Files"); + egui::ScrollArea::vertical().show(&mut columns[1], |ui| { + for file in &state.files { + ui.horizontal(|ui| { + ui.label(format!("{} ({} bytes)", file.filename, file.size)); + ui.small(format!("sha:{} chunks:{}", file.sha_hash, file.chunks)); + if ui.button("Download").clicked() { + if let Some(depot_id) = state.selected_depot { + request_download = Some(( + state.app_id, + depot_id, + state.manifest_input.clone(), + file.filename.clone(), + )); + } + } + }); + } + }); + }); + + if ui.button("Close").clicked() { + close = true; + } + }); + } + + if let Some((appid, depot_id, manifest, file)) = request_download { + let out = std::env::current_dir() + .unwrap_or_else(|_| PathBuf::from(".")) + .join("depot_downloads") + .join(appid.to_string()); + match self + .client + .download_single_file(appid, depot_id, &manifest, &file, &out) + { + Ok(()) => self.status = format!("Downloaded {file} to {}", out.display()), + Err(err) => self.status = format!("Single-file download failed: {err}"), + } + } + + if let Some((appid, depot_id, manifest)) = request_refresh { + match self + .runtime + .block_on(self.client.fetch_manifest_files(appid, depot_id, &manifest)) + { + Ok(files) => { + if let Some(state) = &mut self.depot_browser { + state.files = files; + } + self.status = format!("Loaded manifest {} for depot {}", manifest, depot_id); + } + Err(err) => { + self.status = format!("Failed to fetch manifest files: {err}"); + } + } + } + + if close { + self.depot_browser = None; + } + } + fn auth_ui(&mut self, ui: &mut egui::Ui) { ui.heading("Steam authentication"); ui.horizontal(|ui| { @@ -550,6 +810,17 @@ impl eframe::App for SteamLauncher { ui.separator(); } + ui.horizontal(|ui| { + ui.add( + egui::TextEdit::singleline(&mut self.search_text) + .hint_text("Search library..."), + ); + if !self.search_text.is_empty() && ui.button("x").clicked() { + self.search_text.clear(); + } + }); + ui.separator(); + if self.show_settings { ui.heading("Settings"); ui.label("Steam Library Path"); @@ -634,23 +905,54 @@ impl eframe::App for SteamLauncher { egui::ScrollArea::vertical().show(ui, |ui| { for (app_id, name) in &visible_games { let selected = self.selected_app == Some(*app_id); - ui.horizontal(|ui| { - if ui.selectable_label(selected, name).clicked() { - self.selected_app = Some(*app_id); - } - if self - .library - .iter() - .find(|g| g.app_id == *app_id) - .map(|g| g.update_available) - .unwrap_or(false) - { - ui.colored_label( - egui::Color32::from_rgb(66, 133, 244), - "Update Queued", - ); + let response = ui.selectable_label(selected, name); + if response.clicked() { + self.selected_app = Some(*app_id); + } + + response.context_menu(|ui| { + if ui.button("Verify Integrity").clicked() { + match self.client.verify_game(*app_id) { + Ok(rx) => { + self.download_receiver = Some(rx); + self.active_download_appid = Some(*app_id); + self.status = format!("Started verify for app {}", app_id); + } + Err(err) => { + self.status = format!("Failed to verify {}: {err}", app_id); + } + } + ui.close(); } + + ui.menu_button("Manage", |ui| { + if ui.button("Uninstall").clicked() { + if let Some(game) = self.library.iter().find(|g| g.app_id == *app_id).cloned() { + self.open_uninstall_modal(&game); + } + ui.close(); + } + }); + + ui.menu_button("Advanced", |ui| { + if ui.button("Depot Browser").clicked() { + if let Some(game) = self.library.iter().find(|g| g.app_id == *app_id).cloned() { + self.open_depot_browser(&game); + } + ui.close(); + } + }); }); + + if self + .library + .iter() + .find(|g| g.app_id == *app_id) + .map(|g| g.update_available) + .unwrap_or(false) + { + ui.colored_label(egui::Color32::from_rgb(66, 133, 244), "Update Available"); + } } }); }); @@ -737,7 +1039,7 @@ impl eframe::App for SteamLauncher { if game.update_available { let update_button = egui::Button::new( - egui::RichText::new("Update") + egui::RichText::new("⬇ Update") .color(egui::Color32::WHITE) .strong(), ) @@ -748,7 +1050,7 @@ impl eframe::App for SteamLauncher { .add_enabled(!self.client.is_offline(), update_button) .clicked() { - match self.client.install_game(game.app_id) { + match self.client.update_game(game.app_id) { Ok(rx) => { self.download_receiver = Some(rx); self.active_download_appid = Some(game.app_id); @@ -798,6 +1100,8 @@ impl eframe::App for SteamLauncher { } }); + self.draw_uninstall_modal(ctx); + self.draw_depot_browser_window(ctx); ctx.request_repaint(); } }