diff --git a/Cargo.lock b/Cargo.lock index 41a5625c..f852e415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] +version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -30,12 +21,12 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -43,33 +34,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -82,43 +67,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -138,7 +124,7 @@ dependencies = [ "attribute-derive-macro", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -154,7 +140,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -170,9 +156,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "auxcov" @@ -198,14 +184,11 @@ version = "0.1.0" dependencies = [ "ahash", "auxtools-impl", + "byond-scan", "cc", "ctor", - "dashmap", "fxhash", "inventory", - "lazy_static", - "libc", - "once_cell", "retour", "winapi", ] @@ -214,32 +197,22 @@ dependencies = [ name = "auxtools-impl" version = "0.1.0" dependencies = [ - "hex", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] -name = "backtrace" -version = "0.3.74" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bincode" @@ -258,9 +231,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" @@ -273,12 +246,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", - "serde", + "serde_core", ] [[package]] @@ -293,9 +266,19 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byond-scan" +version = "0.1.0" +source = "git+https://github.com/Absolucy/byond-scan#362cbe051c27aaa6d9a06f0aaa245db8c35ebd00" +dependencies = [ + "iced-x86", + "libc", + "thiserror", +] [[package]] name = "byond_get" @@ -308,15 +291,15 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder" @@ -326,28 +309,26 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -365,10 +346,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -387,23 +369,22 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] @@ -455,9 +436,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -465,27 +446,27 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.4", + "clap_lex 1.1.1", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] @@ -499,15 +480,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "collection_literals" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "color_space" @@ -517,9 +498,9 @@ checksum = "52fdfaf2bee6357023bf7f95b15a8ef0b82759d2bce705cc45efcae9ae10f0ff" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "constant_time_eq" @@ -537,68 +518,84 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -606,18 +603,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -636,26 +633,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.91", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "syn 2.0.119", ] [[package]] name = "debug_server" -version = "2.3.7" +version = "2.4.0" dependencies = [ "auxtools", "bincode", @@ -663,13 +646,21 @@ dependencies = [ "dmasm", "instruction_hooking", "lazy_static", - "libc", "region", "retour", "serde", "winapi", ] +[[package]] +name = "debug_test" +version = "0.1.0" +dependencies = [ + "bincode", + "debug_server", + "serde", +] + [[package]] name = "debugid" version = "0.8.0" @@ -681,18 +672,15 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "derivative" @@ -707,31 +695,31 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.2.7" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "deunicode" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "digest" @@ -746,21 +734,21 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "dmasm" version = "0.1.0" -source = "git+https://github.com/willox/dmasm#71900bef3b209d469762bae72244b1d6abb4be2b" +source = "git+https://github.com/willox/dmasm#6cec0e9a60cb3b3a056daca8c791d36795185154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "dreammaker", "nom", ] @@ -777,7 +765,7 @@ dependencies = [ "foldhash", "get-size", "get-size-derive", - "indexmap 2.7.0", + "indexmap 2.14.2", "interval-tree", "lodepng", "ordered-float", @@ -790,50 +778,62 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "libz-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -865,18 +865,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -884,33 +884,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", @@ -918,7 +918,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -933,9 +932,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -955,14 +954,14 @@ checksum = "13a1bcfb855c1f340d5913ab542e36f25a1c56f57de79022928297632435dec2" dependencies = [ "attribute-derive", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -970,16 +969,35 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] [[package]] name = "globset" -version = "0.4.15" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" dependencies = [ "aho-corasick", "bstr", @@ -994,7 +1012,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "ignore", "walkdir", ] @@ -1006,7 +1024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c5f4731e3050bae8455c1c50d21c6187acac4fb003bc1ee9b6ea7e3e6d90d57" dependencies = [ "chrono", - "clap 4.5.23", + "clap 4.6.7", "crossbeam-channel", "flate2", "globset", @@ -1021,7 +1039,7 @@ dependencies = [ "regex", "rustc-hash", "rustc_version", - "semver 1.0.24", + "semver 1.0.28", "serde", "serde_json", "simplelog", @@ -1038,9 +1056,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -1048,7 +1066,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -1063,15 +1081,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1096,15 +1108,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hmac" @@ -1117,20 +1123,19 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1138,12 +1143,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -1151,9 +1156,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "humansize" @@ -1166,13 +1171,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -1186,16 +1192,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1219,33 +1223,41 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ + "base64 0.23.1", "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1270,21 +1282,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1293,104 +1307,67 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.91", -] - [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1399,9 +1376,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1409,9 +1386,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" dependencies = [ "crossbeam-deque", "globset", @@ -1435,12 +1412,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.17.1", ] [[package]] @@ -1454,9 +1431,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -1468,11 +1445,7 @@ dependencies = [ "auxtools", "cc", "dmasm", - "libc", "retour", - "symbolic-common", - "symbolic-demangle", - "winapi", ] [[package]] @@ -1488,73 +1461,75 @@ source = "git+https://github.com/SpaceManiac/SpacemanDMM?tag=suite-1.10#c1101fb3 [[package]] name = "inventory" -version = "0.3.16" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d80fade88dd420ce0d9ab6f7c58ef2272dde38db874657950f827d4982c817" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.10.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "pkg-config", @@ -1563,37 +1538,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lodepng" -version = "3.10.7" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2dea7cda68e381418c985fd8f32a9c279a21ae8c715f2376adb20c27a0fad3" +checksum = "fe7982db11054edc023a1b424dddcc65be18f71fa46ec6bde2efcfc1fb6b22da" dependencies = [ "crc32fast", "flate2", @@ -1603,9 +1562,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lzma-rs" @@ -1617,11 +1576,22 @@ dependencies = [ "crc", ] +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] @@ -1638,15 +1608,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -1665,22 +1635,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1695,18 +1666,25 @@ dependencies = [ [[package]] name = "msvc-demangler" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", + "itoa", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1731,9 +1709,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1746,11 +1724,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.5.3", "libc", ] @@ -1764,31 +1742,27 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "once_cell" -version = "1.20.2" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1801,20 +1775,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -1848,19 +1822,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "parse-zoneinfo" version = "0.3.1" @@ -1882,26 +1843,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8" dependencies = [ "memchr", - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf" dependencies = [ "pest", "pest_generator", @@ -1909,33 +1869,31 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb" dependencies = [ - "once_cell", "pest", - "sha2", ] [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -1943,9 +1901,9 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", @@ -1953,9 +1911,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -1971,35 +1929,38 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] -name = "pkg-config" -version = "0.3.31" +name = "potential_utf" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -2009,9 +1970,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -2053,9 +2014,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2071,9 +2032,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2086,7 +2047,7 @@ checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" dependencies = [ "quote", "quote-use-macros", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -2098,14 +2059,26 @@ dependencies = [ "derive-where", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha", @@ -2128,14 +2101,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2143,28 +2116,19 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "regex" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2174,9 +2138,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2185,9 +2149,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "region" @@ -2203,11 +2167,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -2221,28 +2185,26 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -2263,39 +2225,38 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ "bytemuck", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.17", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2303,27 +2264,27 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.24", + "semver 1.0.28", ] [[package]] name = "rustix" -version = "0.38.42" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "rustls-pki-types", @@ -2333,25 +2294,19 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2360,15 +2315,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2381,11 +2336,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2396,12 +2351,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags 2.13.2", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2409,9 +2364,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2429,9 +2384,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -2441,34 +2396,45 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2485,9 +2451,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures", @@ -2495,27 +2461,22 @@ dependencies = [ ] [[package]] -name = "sha2" -version = "0.10.8" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "shlex" -version = "1.3.0" +name = "simd-adler32" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simplelog" @@ -2530,18 +2491,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slice-pool2" @@ -2561,31 +2519,25 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -2607,9 +2559,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.12.4" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33e73f154e36ec223c18013f7064a2c120f1162fc086ac9933542def186b00" +checksum = "332615d90111d8eeaf86a84dc9bbe9f65d0d8c5cf11b4caccedc37754eb0dcfd" dependencies = [ "debugid", "memmap2", @@ -2619,9 +2571,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.4" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e51191290147f071777e37fe111800bb82a9059f9c95b19d2dd41bfeddf477" +checksum = "912017718eb4d21930546245af9a3475c9dccf15675a5c215664e76621afc471" dependencies = [ "cpp_demangle", "msvc-demangler", @@ -2642,9 +2594,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -2662,23 +2625,23 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags 2.13.2", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2717,22 +2680,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tera" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" dependencies = [ "chrono", "chrono-tz", @@ -2747,7 +2710,7 @@ dependencies = [ "serde", "serde_json", "slug", - "unic-segment", + "unicode-segmentation", ] [[package]] @@ -2761,12 +2724,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2790,58 +2753,57 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "time" -version = "0.3.37" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2849,9 +2811,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -2859,17 +2821,16 @@ dependencies = [ [[package]] name = "tokio" -version = "1.42.0" +version = "1.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" dependencies = [ - "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2884,9 +2845,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -2894,13 +2855,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -2914,6 +2876,45 @@ dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -2922,9 +2923,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-core", @@ -2932,9 +2933,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -2947,9 +2948,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -2958,60 +2959,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "unicode-ident" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] -name = "unicode-ident" -version = "1.0.14" +name = "unicode-segmentation" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -3027,21 +2984,16 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3056,11 +3008,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" dependencies = [ - "getrandom", + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3096,53 +3050,48 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.99" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" +name = "wasm-bindgen" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.91", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ - "cfg-if", "js-sys", - "once_cell", + "tokio", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3150,28 +3099,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.91", - "wasm-bindgen-backend", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -3195,11 +3147,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3210,41 +3162,72 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[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 2.0.119", ] +[[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 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ + "windows-link", "windows-result", "windows-strings", - "windows-targets", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-result", - "windows-targets", + "windows-link", ] [[package]] @@ -3258,11 +3241,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -3330,24 +3313,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "write16" -version = "1.0.0" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3355,83 +3346,93 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -3440,20 +3441,20 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "zip" -version = "2.2.2" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", @@ -3464,57 +3465,68 @@ dependencies = [ "deflate64", "displaydoc", "flate2", + "getrandom 0.3.4", "hmac", - "indexmap 2.7.0", + "indexmap 2.14.2", "lzma-rs", "memchr", "pbkdf2", - "rand", "sha1", "thiserror", "time", + "xz2", "zeroize", "zopfli", "zstd", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 7dd5c6ec..a32db037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["auxcov", "auxtools", "auxtools-impl", "debug_server", "instruction_hooking", "tests/auxtest", "tests/byond_get", "tests/test_runner"] +members = ["auxcov", "auxtools", "auxtools-impl", "debug_server", "instruction_hooking", "tests/auxtest", "tests/byond_get", "tests/debug_test", "tests/test_runner"] resolver = "2" [workspace.package] @@ -7,13 +7,14 @@ authors = ["Sophie Wallace ", "Lucy "] edition = "2021" license = "MIT" repository = "https://github.com/wilox/auxtools" -rust-version = "1.76" +rust-version = "1.88" version = "0.1.0" [workspace.dependencies] lazy_static = "1" retour = { version = "=0.4.0-alpha.4", default-features = false } dmasm = { git = "https://github.com/willox/dmasm" } +byond-scan = { git = "https://github.com/Absolucy/byond-scan" } [workspace.lints.rust] static_mut_refs = "allow" diff --git a/README.md b/README.md index d53bdabc..ed302f8c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Debug Server - Working with SpaceManiac's [SpacemanDMM](https://github.com/Space *Dissassembly* - When currently stopped at a breakpoint, enter the `#dis` command into the Debug Console in VSC to see the DM bytecode for the current proc. You can also provide it any arbitrary proc path like `#dis /mob/proc/Life`. +## Supported BYOND versions + +516.1659 through 516.1688 on Windows, and 516.1664 through 516.1688 on Linux. Those are the builds the patterns that find BYOND's functions were checked against. A newer build is still tried, and fails at init with the name of whatever stopped matching. An older build, or another major version, is refused at init with an error that names the build. + +The patterns are run by [byond-scan](https://github.com/Absolucy/byond-scan) and live in `auxtools/src/symbols/`. They are copies of the ones in the byond-re repo's `byond_catalog`, which checks each against every local build, so a change to one has to land in both. + +What was run on Linux (516.1687): the `auxtest` suite, and `debug_test`, which sets breakpoints, evaluates expressions and re-attaches through a stand-in for the debugger. The code coverage tool's report was checked on Windows and Linux 1687 against a world with a known set of lines. Stepping and breakpoints with a real debugger attached were not tested on either platform. + ## Dependencies *These instructions were taken directly from tgstation's [rust-g](https://github.com/tgstation/rust-g) documentation.* @@ -67,7 +75,7 @@ Linux: ```sh export PKG_CONFIG_ALLOW_CROSS=1 cargo build --release --target i686-unknown-linux-gnu -# output: target/i686-unknown-linux-gnu/release/libauxtools.so +# output: target/i686-unknown-linux-gnu/release/libauxtools.so,libdebug_server.so,libauxcov.so ``` Windows: diff --git a/auxcov/src/codecov.rs b/auxcov/src/codecov.rs index 51aefb1c..714b5c67 100644 --- a/auxcov/src/codecov.rs +++ b/auxcov/src/codecov.rs @@ -170,7 +170,8 @@ impl Tracker { fn finalize(&mut self) -> Result<(), Vec> { let mut errors_option = None; for context in &self.contexts { - let result = context.finalize(); // dropping the results because what can ya do? + let result = context.finalize(); // dropping the results because + // what can ya do? if let Err(error) = result { match &mut errors_option { None => { @@ -195,8 +196,9 @@ impl Tracker { impl Drop for Tracker { fn drop(&mut self) { - let _result = self.finalize(); // dropping the result here because what can ya - // do? + let _result = self.finalize(); // dropping the result here because what + // can ya + // do? } } diff --git a/auxcov/src/lib.rs b/auxcov/src/lib.rs index 931166e4..7cadc054 100644 --- a/auxcov/src/lib.rs +++ b/auxcov/src/lib.rs @@ -2,8 +2,6 @@ mod codecov; -use std::any::{Any, TypeId}; - use auxtools::*; use codecov::Tracker; use instruction_hooking::INSTRUCTION_HOOKS; @@ -15,14 +13,13 @@ where unsafe { let hooks = INSTRUCTION_HOOKS.get_mut(); - let tracker_tid = TypeId::of::(); - let tracker_option = hooks.iter_mut().find(|hook| (*hook).as_ref().type_id() == tracker_tid); + // `type_id()` on the boxed hook itself would give the id of the trait + // object, never `Tracker`'s, so ask the hook for its concrete + // value as `Any` first. + let tracker_option = hooks.iter_mut().find_map(|hook| hook.as_mut().as_any().downcast_mut::()); match tracker_option { - Some(existing_hook) => { - let mut_hook = existing_hook.as_mut(); - let any_hook = mut_hook.as_any(); - let existing_tracker = any_hook.downcast_mut::().unwrap(); + Some(existing_tracker) => { f(existing_tracker); } None => { diff --git a/auxtools-impl/Cargo.toml b/auxtools-impl/Cargo.toml index 42b25946..98297de8 100644 --- a/auxtools-impl/Cargo.toml +++ b/auxtools-impl/Cargo.toml @@ -15,7 +15,6 @@ doctest = false [dependencies] proc-macro2 = "1.0" quote = "1.0" -hex = "0.4" syn = { version = "2", features = ["full"] } [lints] diff --git a/auxtools-impl/src/lib.rs b/auxtools-impl/src/lib.rs index 145481c7..c20d8bfb 100644 --- a/auxtools-impl/src/lib.rs +++ b/auxtools-impl/src/lib.rs @@ -2,60 +2,13 @@ mod versioned_struct; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, spanned::Spanned, Lit}; - -fn from_signature(s: String) -> Vec> { - s.trim() - .split(' ') - .map(|byte| { - let byte = byte.trim(); - match byte.len() { - 2 => { - if byte == "??" { - None - } else { - hex::decode(byte).map(|decoded_byte| decoded_byte[0]).ok() - } - } - _ => None - } - }) - .collect() -} +use syn::{spanned::Spanned, Lit}; #[proc_macro_attribute] pub fn versioned(attr: TokenStream, item: TokenStream) -> TokenStream { versioned_struct::versioned(attr.into(), item.into()).into() } -#[proc_macro] -pub fn convert_signature(input: TokenStream) -> TokenStream { - let string = parse_macro_input!(input as Lit); - let string = match string { - Lit::Str(lit) => lit.value(), - _ => panic!("not string input") - }; - - let streams: Vec = from_signature(string) - .into_iter() - .map(|x| match x { - Some(byte) => { - quote! { - Some(#byte as u8) - } - } - None => { - quote! { None } - } - }) - .collect(); - - let result = quote! { - &[ #( #streams, )* ] - }; - result.into() -} - fn extract_args(a: &syn::FnArg) -> &syn::PatType { match a { syn::FnArg::Typed(p) => p, @@ -185,14 +138,11 @@ pub fn pin_dll(attr: TokenStream) -> TokenStream { /// /// Here we define a hook that multiplies a number passed to it by two. /// It can now be used to hook procs, for example -/// `hooks::hook("/proc/double_up", double_up);` +/// `Proc::find("/proc/double_up").unwrap().hook(double_up);` /// ```ignore /// #[hook] /// fn double_up(num: Value) { -/// if let Some(num) = num.as_number() { -/// Value::from(num * 2.0); -/// } -/// Value::NULL +/// Ok(Value::from(num.as_number()? * 2.0)) /// } /// ``` /// @@ -201,9 +151,9 @@ pub fn pin_dll(attr: TokenStream) -> TokenStream { /// ```ignore /// #[hook("/mob/proc/on_honked")] /// fn on_honked(honker: Value) { -/// src.call("gib", &[]); -/// honker.call("laugh", &[]); -/// Value::NULL +/// src.call("gib", &[])?; +/// honker.call("laugh", &[])?; +/// Ok(Value::NULL) /// } /// ``` #[proc_macro_attribute] diff --git a/auxtools/Cargo.toml b/auxtools/Cargo.toml index b9593533..b51cd8df 100644 --- a/auxtools/Cargo.toml +++ b/auxtools/Cargo.toml @@ -16,20 +16,15 @@ cc = "1.0" [dependencies] auxtools-impl = { path = "../auxtools-impl", version = "0.1.0", package = "auxtools-impl" } -once_cell = "1" +byond-scan = { workspace = true } inventory = "0.3" -lazy_static = { workspace = true } -dashmap = "6" ahash = "0.8" fxhash = "0.2" ctor = "0.2" retour = { workspace = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "psapi", "processthreadsapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" +winapi = { version = "0.3.9", features = ["libloaderapi"] } [lints] workspace = true diff --git a/auxtools/build.rs b/auxtools/build.rs index 80947482..50e87e83 100644 --- a/auxtools/build.rs +++ b/auxtools/build.rs @@ -1,4 +1,8 @@ fn main() { + println!("cargo:rerun-if-changed=src/hooks.cpp"); + println!("cargo:rerun-if-changed=src/hooks.h"); + println!("cargo:rerun-if-changed=src/raw_types/funcs.cpp"); + cc::Build::new() .include("src/") .file("src/hooks.cpp") diff --git a/auxtools/src/byond_ffi.rs b/auxtools/src/byond_ffi.rs index 30437943..21b8dbeb 100644 --- a/auxtools/src/byond_ffi.rs +++ b/auxtools/src/byond_ffi.rs @@ -74,9 +74,9 @@ pub fn byond_return(value: Option>) -> *const c_char { /// /// # Examples /// ```ignore -/// // byond_ffi_fn! { my_proc(_input) { -/// // Some("Hello, BYOND!".to_owned()) -/// // } +/// byond_ffi_fn! { my_proc(_input) { +/// Some("Hello, BYOND!".to_owned()) +/// } } /// ``` #[macro_export] macro_rules! byond_ffi_fn { diff --git a/auxtools/src/bytecode_manager.rs b/auxtools/src/bytecode_manager.rs index 6b90015b..2e94a97c 100644 --- a/auxtools/src/bytecode_manager.rs +++ b/auxtools/src/bytecode_manager.rs @@ -59,7 +59,7 @@ pub fn shutdown() { let proc = Proc::from_id(id).unwrap(); unsafe { - raw_types::misc::set_bytecode(*(*proc.entry).metadata.bytecode(), ptr, len); + raw_types::misc::set_bytecode(*(*proc.entry()).metadata.bytecode(), ptr, len); } } @@ -104,6 +104,6 @@ pub fn set_bytecode(proc: &Proc, mut bytecode: Vec) { let len = u16::try_from(len).unwrap(); unsafe { - raw_types::misc::set_bytecode(*(*proc.entry).metadata.bytecode(), ptr, len); + raw_types::misc::set_bytecode(*(*proc.entry()).metadata.bytecode(), ptr, len); } } diff --git a/auxtools/src/debug.rs b/auxtools/src/debug.rs index 6f036c91..bc45db7f 100644 --- a/auxtools/src/debug.rs +++ b/auxtools/src/debug.rs @@ -36,8 +36,8 @@ impl StackFrame { let src = Value::from_raw((*instance).src); let dot = Value::from_raw(*(*context).dot()); - // Make sure to handle arguments/locals with no names (when there are more - // values than names) + // Make sure to handle arguments/locals with no names (when there are + // more values than names) let args = (0..(*instance).args_count()) .map(|i| { let name = param_names.get(i as usize).cloned(); diff --git a/auxtools/src/hooks.cpp b/auxtools/src/hooks.cpp index 652e86b1..ada9354e 100644 --- a/auxtools/src/hooks.cpp +++ b/auxtools/src/hooks.cpp @@ -3,11 +3,28 @@ // The type of the func defined in Byond using Runtime_Ptr = void(*)(char *pError); -using CallProcById_Ptr = Value(LINUX_REGPARM3 *)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); +// Plain cdecl on both platforms. This carried regparm(3) on Linux, which was +// never right: `call_proc_by_id` reads all twelve of its dwords off the stack, the +// first being the hidden buffer the Value return is written through, and ends +// `retn 4` to pop that dword itself. Declaring regparm(3) made GCC pass the buffer +// in eax and `usr` in edx:ecx, which shifted every argument the trampoline read +// (the proc id it looked up was really the output pointer, so no #[hook] ever +// matched) and left nothing on the stack for `retn 4` to pop, so the stack drifted +// until an SSE store faulted somewhere inside BYOND. Read byte-identical on +// 516.1669 and 516.1687. +// +// The last two arguments are a completion callback and a value to hand it. BYOND +// pushes the result's two dwords and then the value, which is the same thing as +// passing a Value. Plain cdecl on both platforms. +using CallProc_Callback = void(*)(Value, uint32_t); +using CallProcById_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, CallProc_Callback, uint32_t); // The type of the hook defined in hooks.rs using CallProcById_Hook_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); +// The bit of `proc_type` that says the caller is not going to look at the result +static const uint32_t PROC_TYPE_DISCARD_RESULT = 0x04; + extern "C" { // The ptr everybody else sees Runtime_Ptr runtime_byond = nullptr; @@ -15,6 +32,9 @@ extern "C" { // The original function - set by rust after hooking Runtime_Ptr runtime_original = nullptr; CallProcById_Ptr call_proc_by_id_original = nullptr; + + // Lives in funcs.cpp + extern void (*dec_ref_count_byond)(Value); } // If the top of this stack is true, we replace byond's runtime exceptions with our own @@ -42,33 +62,53 @@ extern "C" uint8_t call_proc_by_id_hook( Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, Value* args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2); + uint32_t args_count, + CallProc_Callback callback, + uint32_t callback_value); -// A little function to handle the odd calling convention on Linux and pass-through to our rust hook +// Stands in for `call_proc_by_id` itself, so its shape has to match that function +// exactly (see the note on `CallProcById_Ptr` above). Passes through to our rust hook. // Used on Windows too -extern "C" Value LINUX_REGPARM3 call_proc_by_id_hook_trampoline( +extern "C" Value call_proc_by_id_hook_trampoline( Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, Value* args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2 + uint32_t args_count, + CallProc_Callback callback, + uint32_t callback_value ) { Value ret; - if (call_proc_by_id_hook(&ret, usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2)) { - clean(ret); - return ret; - } else { - return call_proc_by_id_original(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + // A shim running with `RuntimeContext(true)` wants the errors its own BYOND + // function raises, not the ones raised inside a DM proc that function calls. + // Those have to take BYOND's normal path so they get reported and reach the + // callee's own `try`. Every DM proc call passes through here, so this is the + // one place that can hand the called proc a clean context. + RuntimeContext runtime_scope(false); + + if (!call_proc_by_id_hook(&ret, usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value)) { + return call_proc_by_id_original(usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value); + } + clean(ret); + + // The hook ran instead of `exec_proc`, so it owes the caller what `exec_proc` + // does when a proc ends. A caller that passed a callback gets the result + // through it, and one that set the discard bit doesn't want it at all. Both of + // them get null back and never release anything, so the result's reference is + // ours to drop. Done here and not in rust so a callback that throws doesn't + // have to unwind through a rust frame. + if (callback != nullptr) { + callback(ret, callback_value); + } + if (callback != nullptr || (proc_type & PROC_TYPE_DISCARD_RESULT)) { + dec_ref_count_byond(ret); + ret = Value{0, 0}; } - //return call_proc_by_id_hook(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + return ret; } diff --git a/auxtools/src/hooks.rs b/auxtools/src/hooks.rs index b27e8279..5226275c 100644 --- a/auxtools/src/hooks.rs +++ b/auxtools/src/hooks.rs @@ -1,6 +1,8 @@ use std::{ cell::RefCell, + collections::HashMap, ffi::{c_void, CStr}, + hash::BuildHasherDefault, os::raw::c_char }; @@ -32,12 +34,12 @@ extern "C" { usr: raw_types::values::Value, proc_type: u32, proc_id: raw_types::procs::ProcId, - unk_0: u32, + override_depth: u32, src: raw_types::values::Value, args: *mut raw_types::values::Value, args_count_l: usize, - unk_1: u32, - unk_2: u32 + callback: u32, + callback_value: u32 ) -> raw_types::values::Value; } @@ -114,24 +116,32 @@ pub fn shutdown() { pub type ProcHook = fn(&Value, &Value, Vec) -> DMResult; -thread_local! { - static PROC_HOOKS: RefCell> = RefCell::new(FxHashMap::default()); -} +/// Lets a `RefCell` live in a plain `static`. +/// +/// Everything here is only touched from BYOND's game thread: its proc calls and +/// init/shutdown, which it also drives. The table used to be a `thread_local!`, +/// but in a dlopen'd .so every access goes through `__tls_get_addr`, and it is +/// read on every single DM proc call. +struct GameThreadOnly(T); + +// SAFETY: only the game thread ever reaches these statics, see above +unsafe impl Sync for GameThreadOnly {} + +static PROC_HOOKS: GameThreadOnly>> = + GameThreadOnly(RefCell::new(HashMap::with_hasher(BuildHasherDefault::new()))); fn hook_by_id(id: raw_types::procs::ProcId, hook: ProcHook, hook_path: String) -> Result<(), HookFailure> { - PROC_HOOKS.with(|h| { - let mut map = h.borrow_mut(); - if let std::collections::hash_map::Entry::Vacant(e) = map.entry(id) { - e.insert((hook, hook_path)); - Ok(()) - } else { - Err(HookFailure::AlreadyHooked) - } - }) + let mut map = PROC_HOOKS.0.borrow_mut(); + if let std::collections::hash_map::Entry::Vacant(e) = map.entry(id) { + e.insert((hook, hook_path)); + Ok(()) + } else { + Err(HookFailure::AlreadyHooked) + } } pub fn clear_hooks() { - PROC_HOOKS.with(|h| h.borrow_mut().clear()); + PROC_HOOKS.0.borrow_mut().clear(); } pub fn hook>(name: S, hook: ProcHook) -> Result<(), HookFailure> { @@ -162,53 +172,50 @@ extern "C" fn call_proc_by_id_hook( usr_raw: raw_types::values::Value, _proc_type: u32, proc_id: raw_types::procs::ProcId, - _unknown1: u32, + _override_depth: u32, src_raw: raw_types::values::Value, args_ptr: *mut raw_types::values::Value, num_args: usize, - _unknown2: u32, - _unknown3: u32 + // the trampoline deals with these two once we return + _callback: u32, + _callback_value: u32 ) -> u8 { - match PROC_HOOKS.with(|h| match h.borrow().get(&proc_id) { - Some((hook, path)) => { - let (src, usr, args) = unsafe { - ( - Value::from_raw(src_raw), - Value::from_raw(usr_raw), - // Taking ownership of args here - std::slice::from_raw_parts(args_ptr, num_args) - .iter() - .map(|v| Value::from_raw_owned(*v)) - .collect() - ) - }; - - let result = hook(&src, &usr, args); - - match result { - Ok(r) => { - let result_raw = r.raw; - // Stealing our reference out of the Value - std::mem::forget(r); - Some(result_raw) - } - Err(e) => { - Proc::find("/proc/auxtools_stack_trace") - .unwrap() - .call(&[&Value::from_string(format!("{} HookPath: {}", e.message.as_str(), path.as_str())).unwrap()]) - .unwrap(); - Some(Value::NULL.raw) - } - } + // copy the fn out so no borrow is held while the hook runs DM code, which + // can call back into here or register hooks + let Some(hook) = PROC_HOOKS.0.borrow().get(&proc_id).map(|(hook, _)| *hook) else { + return 0; + }; + + let (src, usr, args) = unsafe { + ( + Value::from_raw(src_raw), + Value::from_raw(usr_raw), + // Taking ownership of args here + std::slice::from_raw_parts(args_ptr, num_args) + .iter() + .map(|v| Value::from_raw_owned(*v)) + .collect() + ) + }; + + let result = match hook(&src, &usr, args) { + Ok(r) => { + let result_raw = r.raw; + // Stealing our reference out of the Value + std::mem::forget(r); + result_raw } - None => None - }) { - Some(result) => { - unsafe { - *ret = result; - } - 1 + Err(e) => { + let path = PROC_HOOKS.0.borrow()[&proc_id].1.clone(); + Proc::find("/proc/auxtools_stack_trace") + .unwrap() + .call(&[&Value::from_string(format!("{} HookPath: {}", e.message.as_str(), path)).unwrap()]) + .unwrap(); + Value::NULL.raw } - None => 0 + }; + unsafe { + *ret = result; } + 1 } diff --git a/auxtools/src/lib.rs b/auxtools/src/lib.rs index a12065a5..cc141ea0 100644 --- a/auxtools/src/lib.rs +++ b/auxtools/src/lib.rs @@ -3,7 +3,9 @@ //#[cfg(not(target_pointer_width = "32"))] // compile_error!("Auxtools must be compiled for a 32-bit target"); -mod byond_ffi; +// public so `byond_ffi_fn!` can reach it from another crate +#[doc(hidden)] +pub mod byond_ffi; mod bytecode_manager; pub mod debug; mod hooks; @@ -12,23 +14,25 @@ mod list; mod proc; pub mod raw_types; mod runtime; -pub mod sigscan; mod string; mod string_intern; +mod symbols; mod value; mod value_from; pub mod version; mod weak_value; -use std::{ - ffi::c_void, - sync::atomic::{AtomicBool, Ordering} +use std::sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock }; pub use auxtools_impl::{full_shutdown, hook, init, pin_dll, runtime_handler, shutdown}; +/// For crates that need to find something else in BYOND's binary. +pub use byond_scan; /// Used by the [pin_dll] macro to set dll pinning pub use ctor; -pub use hooks::{CompileTimeHook, RuntimeErrorHook}; +pub use hooks::{CompileTimeHook, HookFailure, ProcHook, RuntimeErrorHook}; use init::{get_init_level, set_init_level, InitLevel}; pub use init::{FullInitFunc, FullShutdownFunc, PartialInitFunc, PartialShutdownFunc}; /// Used by the [hook](attr.hook.html) macro to aggregate all compile-time hooks @@ -39,6 +43,7 @@ pub use raw_types::variables::VariableNameIdTable; pub use runtime::{DMResult, Runtime}; pub use string::StringRef; pub use string_intern::InternedString; +pub use symbols::find_recipe; pub use value::Value; pub use weak_value::WeakValue; @@ -47,163 +52,11 @@ pub use weak_value::WeakValue; #[cfg(windows)] extern crate winapi; -#[cfg(windows)] -pub const BYONDCORE: &str = "byondcore.dll"; -#[cfg(windows)] -signatures! { - get_proc_array_entry => version_dependent_signature!( - 1630.. => (call, "E8 ?? ?? ?? ?? 8B 4D 0C 8B D0 83 C4 04 89 55 DC 8D 46 10 F6 C1 01"), - ..1630 => (call, "E8 ?? ?? ?? ?? 8B C8 8D 45 ?? 6A 01 50 FF 76 ?? 8A 46 ?? FF 76 ?? FE C0") - ), - get_string_id => universal_signature!("55 8B EC 8B 45 ?? 83 EC ?? 53 56 8B 35 ?? ?? ?? ?? 57 85 C0 75 ?? 68 ?? ?? ?? ??"), - call_proc_by_id => version_dependent_signature!( - 1648.. => "55 8B EC 81 EC 9C 00 00 00 A1 ?? ?? ?? ?? 33 C5 89 45 ?? 8B 55 ?? 8B 45 ??", - 1602..1648 => "55 8B EC 81 EC 98 00 00 00 A1 ?? ?? ?? ?? 33 C5 89 45 FC 8B 55", - ..1602 => (call, "E8 ?? ?? ?? ?? 83 C4 2C 89 45 F4 89 55 F8 8B 45 F4 8B 55 F8 5F 5E 5B 8B E5 5D C3 CC 55 8B EC 83 EC 0C 53 8B 5D 10 8D 45 FF") - ), - get_variable => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 8B 75 ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 28 A1 ?? ?? ?? ?? 33 C5 89 45 F0 53 56 57 50 8D 45 F4 64 A3 00 00 00 00 8B 5D", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 8B 4D ?? 0F B6 C1 48 83 F8 ?? 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 ?? FF 75 ?? E8" - ), - set_variable => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 08 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 8B 4D 08 0F B6 C1 48 57 8B 7D 10 83 F8 53 0F ?? ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 18 FF 75 14 57 FF 75 0C E8 ?? ?? ?? ?? 83 C4 10 5F 5D C3"), - get_string_table_entry => universal_signature!("55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 10 A1"), - call_datum_proc_by_name => version_dependent_signature!( - 1615.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 1C 8D 45 F3 8B 7D 18 8B 5D 10 6A 00", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 14 8D 45 F3 8B 5D 10 6A 01", - ..1602 => "55 8B EC 83 EC 0C 53 8B 5D 10 8D 45 FF 56 8B 75 14 57 6A 01 50 FF 75 1C C6 45 FF 00 FF 75 18 6A 00 56" - ), - /* ..1614 "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74 ?? 57 FF 75 ??" - 1615.. "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 13 85 FF 74 0F 8B 4D 14 57 53 56" */ - dec_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74"), - /* ..1614 "E8 ?? ?? ?? ?? FF 77 ?? FF 77 ?? E8 ?? ?? ?? ?? 8D 77 ?? 56 E8 ?? ?? ?? ??" - 1615.. "E8 ?? ?? ?? ?? FF 73 14 FF 73 10 E8 ?? ?? ?? ?? 8D 73 30 56 E8" */ - inc_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? FF ?? ?? FF ?? ?? E8 ?? ?? ?? ?? 8D ?? ?? 56 E8"), - get_assoc_element => version_dependent_signature!( - 1614.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 51 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 10 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 51 8B 4D 08 C6 45 FF 00 80 F9 05 76 11 80 F9 21 74 10 80 F9 0D 74 0B 80 F9 0E 75 65 EB 04 84 C9 74 5F 6A 00 8D 45 FF 50 FF 75 0C 51 6A 00 6A 7B" - ), - set_assoc_element => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 1C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 80 FB 0F", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 83 EC 14 8B 4D 08 C6 45 FF 00 80 F9 05 76 15 80 F9 21 74 14 80 F9 0D 74 0F 80 F9 0E 0F 85 ?? ?? ?? ?? EB 04 84 C9 74 7A 6A 00" - ), - create_list => version_dependent_signature!( - 1615.. => "55 8B EC 8B 0D ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 0D ?? ?? ?? ?? 8B 34 88 81 FE FF FF 00 00 0F 85 EC 00 00 00 8B 35 ?? ?? ?? ?? 81 FE FF FF FF 00 75 1B 56 68 00 01 00 00 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 83 C4 0C B8 FF FF 00 00 5E 5D C3", - ..1614 => "55 8B EC 8B ?? ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 ?? ?? ?? ?? ?? 8B 34 88 81 FE ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 8B F1 81 F9 ?? ?? ?? ?? 75 1B 51 68 ?? ?? ?? ?? 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 83 C4 0C B8 ?? ?? ?? ?? 5E 5D C3" - ), - append_to_list => version_dependent_signature!( - 1648.. => "55 8B EC 8B 4D ?? 0F B6 C1 48 56 57 83 F8 54 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ??", - 1615..1648 => "55 8B EC 8B 4D 08 0F B6 C1 48 56 57 83 F8 53 0F 87 AA 00 00 00 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 0C E8 ?? ?? ?? ?? 8B F0 83 C4 04 85 F6 0F 84 94 00 00 00 8B 46 0C 40 50 56 E8 ?? ?? ?? ?? 8B 7E 0C 83 C4 08 85", - ..1614 => "55 8B EC 8B 4D 08 0F B6 C1 48 56 83 F8 53 0F 87 ?? ?? ?? ?? 0F B6 ?? ?? ?? ?? ?? FF 24 ?? ?? ?? ?? ?? FF 75 0C E8 ?? ?? ?? ?? 8B F0 83 C4 04 85 F6 0F 84 ?? ?? ?? ?? 8B 46 0C 40 50 56 E8 ?? ?? ?? ?? 8B 56 0C 83 C4 08 85 D2" - ), - remove_from_list => version_dependent_signature!( - 1648.. => "55 8B EC 83 EC 08 53 8B 5D ?? 0F B6 C3 48 56 57 83 F8 54 0F 87 ?? ?? ?? ??", - 1615..1648 => "55 8B EC 83 EC 08 53 8B 5D 08 0F B6 C3 48 56 57 83 F8 53 0F 87 23 01 00 00 0F B6 80 ?? ?? ?? ?? 8B 4D 10 FF 24 85 ?? ?? ?? ?? 8B 7D 0C 6A 0F 57 53 E8 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 10 85 C0 0F 84 02 01 00 00 8B 08 8B 40 0C 85 C0 0F 84 F5 00 00 00 8B 75 14 8B 5D 10", - ..1614 => "55 8B EC 8B 4D 08 83 EC 0C 0F B6 C1 48 53 83 F8 53 0F 87 ?? ?? ?? ?? 0F B6 ?? ?? ?? ?? ?? 8B 55 10 FF 24 ?? ?? ?? ?? ?? 6A 0F FF 75 0C 51 E8 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 10 85 C0 0F 84 ?? ?? ?? ?? 8B 48 0C 8B 10 85 C9 0F 84 ?? ?? ?? ?? 8B 45 14 8B 5D 10" - ), - get_length => universal_signature!("55 8B EC 8B 4D ?? 83 EC ?? 0F B6 C1 48 53 56 57 83 F8 ?? 0F 87 ?? ?? ?? ??" - ), - get_misc_by_id => version_dependent_signature!( - 1615.. => (call, "E8 ?? ?? ?? ?? 83 C4 04 85 C0 74 08 0F B7 38 8B 70 08 EB 04 33 FF 33 F6 0F B7 C7 50 89 45 F8 E8 ?? ?? ?? ??"), - ..1614 => (call, "E8 ?? ?? ?? ?? 83 C4 04 85 C0 75 ?? FF 75 ?? E8 ?? ?? ?? ?? FF 30 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? A1 ?? ?? ?? ??") - ), - runtime => universal_signature!(call, "E8 ?? ?? ?? ?? 83 C4 04 8B 85 ?? ?? ?? ?? 0F B6 C0 51 66 0F 6E C0 0F 5B C0"), - suspended_procs => version_dependent_signature!( - 1615.. => (2, "8B 1D ?? ?? ?? ?? 56 8B 75 ?? 57 8B 3D ?? ?? ?? ?? 89 7D ?? 8B 86"), - ..1614 => (1, "A1 ?? ?? ?? ?? 8B D8 89 45 ?? 89 75 ?? 3B DA 73 ?? 8D 0C ?? D1 E9 8B 04 ??") - ), - suspended_procs_buffer => version_dependent_signature!( - 1615.. => (2, "8B 3D ?? ?? ?? ?? 89 7D ?? 8B 86 ?? ?? ?? ?? 89 45 ?? A1 ?? ?? ?? ?? 8B"), - ..1614 => (2, "8B 35 ?? ?? ?? ?? 8B 80 ?? ?? ?? ?? 57 8B 3D ?? ?? ?? ?? 8B D7 89 45 ??") - ), - to_string => version_dependent_signature!( - 1686.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 38 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 0F B6 C3 48 83 F8 58 0F 87", - 1648..1686 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 38 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 75 ?? 8B 5D ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 ?? 8B 5D ?? BF 00 90 00 00 0F B6 C3 48 83 F8 53 0F 87", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 3C 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75", - 1585..1602 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 1D ?? ?? ?? ??", - 1561..1585 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ?? 0F B6 C1", - 1543..1561 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??", - ..1543 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 10 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 0F B6 C3"), - - current_execution_context => version_dependent_signature!( - 1615.. => (1, "A1 ?? ?? ?? ?? 56 53 6A 00 8B 00 57 6A ?? 89 4D ?? FF 70 ?? 8B 4D ?? FF"), - ..1614 => (1, "A1 ?? ?? ?? ?? FF 75 ?? 89 4D ?? 8B 4D ?? 8B 00 6A 00 52 6A 12 FF 70 ??") - ), - variable_names => version_dependent_signature!( - 1615.. => (2, "8B 0D ?? ?? ?? ?? 0F B7 C2 89 45 08 8B 04 87 39 34 81 74 35 0F B7 F3 66 3B DA 76 25 0F 1F 84 00 00 00 00 00"), - ..1614 => (2, "8B 1D ?? ?? ?? ?? 2B 0C ?? 8B 5D ?? 74 ?? 85 C9 79 ?? 0F B7 D0 EB ?? 83 C0 02") - ) -} - -#[cfg(unix)] -pub const BYONDCORE: &str = "libbyond.so"; -#[cfg(unix)] -signatures! { - get_proc_array_entry => version_dependent_signature!( - 1584.. => (call, "E8 ?? ?? ?? ?? 0F B7 F6 89 C7 89 B5 ?? ?? ?? ?? 89 34 24 E8 ?? ?? ?? ??"), - ..1584 => (call, "E8 ?? ?? ?? ?? 8B 00 89 04 24 E8 ?? ?? ?? ?? 8B 00 89 44 24 ?? 8D 45 ??") - ), - get_string_id => universal_signature!("55 89 E5 57 56 89 CE 53 89 D3 83 EC 5C 8B 55 ?? 85 C0 88 55 ?? 0F 84 ?? ?? ?? ??"), - call_proc_by_id => universal_signature!(call, "E8 ?? ?? ?? ?? 8B 45 ?? 8B 55 ?? 89 45 ?? 89 55 ?? 8B 55 ?? 8B 4D ?? 8B 5D ??"), - get_variable => universal_signature!("55 89 E5 81 EC C8 00 00 00 8B 55 ?? 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ??"), - set_variable => version_dependent_signature!( - 1560.. => (call, "E8 ?? ?? ?? ?? 8B 45 ?? 8D 65 ?? 5B 5E 5F 5D C3 8D B4 26 00 00 00 00 8B 40 ??"), - 1543..1560 => "55 89 E5 81 EC A8 00 00 00 8B 55 ?? 89 5D ?? 8B 4D ?? 89 7D ?? 8B 5D ??", - ..1543 => "55 89 E5 81 EC A8 00 00 00 8B 55 ?? 8B 45 ?? 89 5D ?? 8B 5D ?? 89 7D ??" - ), - - get_string_table_entry => universal_signature!("55 89 E5 83 EC 18 8B 45 ?? 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 ??"), - call_datum_proc_by_name => version_dependent_signature!( - 1606.. => "55 89 E5 57 56 89 CE 53 89 D3 83 EC ?? 0F B6 55 ?? 89 45 ?? 8B 45 ?? 8B 7D ?? C6 45 E7 ?? 0F B6 CA 89 45 B0 8D 45 ?? 89 44 24 ?? 8B 45 ?? 89 ?? BC 31 C9 88 ?? BB 8B 55 ?? C7 44 24 ?? 01 00 00 00", - ..1606 => "55 89 E5 57 56 53 83 EC 5C 8B 55 ?? 0F B6 45 ?? 8B 4D ?? 8B 5D ?? 89 14 24 8B 55 ?? 88 45 ?? 0F B6 F8 8B 75 ?? 8D 45 ?? 89 44 24 ?? 89 F8 89 4C 24 ?? 31 C9 C6 45 ?? 00 C7 44 24 ?? 01 00 00 00" - ), - - dec_ref_count => version_dependent_signature!( - 1543.. => (call, "E8 ?? ?? ?? ?? C7 06 00 00 00 00 C7 46 ?? 00 00 00 00 A1 ?? ?? ?? ?? 0F B7 50 ??"), - ..1543 => (call, "E8 ?? ?? ?? ?? 8B 4D ?? C7 44 24 ?? 00 00 00 00 C7 44 24 ?? 00 00 00 00 89 0C 24") - ), - inc_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? 8B 43 ?? 80 48 ?? 04 8B 5D ?? 8B 75 ?? 8B 7D ?? 89 EC 5D"), - get_assoc_element => version_dependent_signature!( - 1602.. => "55 89 E5 83 EC ?? ?? ?? ?? ?? 5D F4 89 D3 89 75 F8 89 D6 89 7D FC 89 CF 89 45 B4 0F 84 B7 00 00 ??", - ..1602 => "55 89 E5 83 EC 68 89 4D ?? B9 7B 00 00 00 89 5D ?? 89 D3 89 75 ?? 89 C6" - ), - - set_assoc_element => version_dependent_signature!( - 1602.. => "55 89 E5 83 EC 68 89 75 F8 8B 75 08 89 5D F4 89 C3 8B 45 0C 89 7D FC 80 FB 3C 89 D7 88 5D BF 89 ??", - ..1602 => "55 B9 7C 00 00 00 89 E5 83 EC 58 89 7D ?? 8B 7D ?? 89 5D ?? 89 C3 8B 45 ??" - ), - - create_list => universal_signature!("55 89 E5 57 56 53 83 EC 2C A1 ?? ?? ?? ?? 8B 75 ?? 85 C0 0F 84 ?? ?? ?? ??"), - append_to_list => universal_signature!("55 89 E5 83 EC 38 3C 54 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ?? 89 7D ?? 76 ??"), - remove_from_list => universal_signature!("55 89 E5 83 EC 48 3C 54 89 5D ?? 89 C3 89 75 ?? 8B 75 ?? 89 7D ?? 8B 7D ??"), - get_length => universal_signature!("55 89 E5 57 56 53 83 EC 6C 8B 45 ?? 8B 5D ?? 3C 54 76 ?? 31 F6 8D 65 ??"), - get_misc_by_id => universal_signature!(call, "E8 ?? ?? ?? ?? 0F B7 55 ?? 03 1F 0F B7 4B ?? 89 8D ?? ?? ?? ?? 0F B7 5B ??"), - runtime => universal_signature!(call, "E8 ?? ?? ?? ?? 31 C0 8D B4 26 00 00 00 00 8B 5D ?? 8B 75 ?? 8B 7D ?? 89 EC"), - suspended_procs => universal_signature!(1, "A3 ?? ?? ?? ?? 8D 14 ?? 73 ?? 8D 74 26 00 83 C0 01 8B 14 ?? 39 C3 89 54 ?? ??"), - suspended_procs_buffer => universal_signature!(2, "89 35 ?? ?? ?? ?? C7 04 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 45 ?? 83 C0 08"), - to_string => version_dependent_signature!( - 1602.. => "55 89 E5 83 ?? ?? 89 5D F4 8D ?? ?? 89 75 F8 89 7D FC 80 ?? ?? ?? ?? ?? B8", - 1560..1602 => (call, "E8 ?? ?? ?? ?? 89 04 24 E8 ?? ?? ?? ?? 8B 00 8D 4D ?? 89 0C 24"), - 1543..1560 => "55 89 E5 83 EC 68 A1 ?? ?? ?? ?? 8B 15 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 89 5D ??", - ..1543 => "55 89 E5 83 EC 58 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ?? 89 7D ?? 80 FB 54" - ), - current_execution_context => universal_signature!(1, "A1 ?? ?? ?? ?? C7 44 24 ?? 00 00 00 00 C7 44 24 ?? 00 00 00 00 89 74 24"), - variable_names => version_dependent_signature!( - 1543.. => (1, "A1 ?? ?? ?? ?? 8B 13 8B 39 8B 75 ?? 8B 14 ?? 89 7D ?? 8B 3C ?? 83 EE 02"), - ..1543 => (2, "8B 35 ?? ?? ?? ?? 89 5D ?? 0F B7 08 89 75 ?? 66 C7 45 ?? 00 00 89 7D ??") - ) -} pub static PIN_DLL: AtomicBool = AtomicBool::new(true); +/// wrapper for [`byond_scan::MODULE_NAME`](byond_scan::MODULE_NAME) +pub const BYONDCORE: &str = byond_scan::MODULE_NAME; + // This strange section of code retrieves our DLL using the init function's // address. This increments the DLL reference count, which prevents unloading. #[cfg(windows)] @@ -230,101 +83,54 @@ fn pin_dll() -> Result<(), ()> { Ok(()) } +// The first failed init. Trying again would run setup on top of what the +// failed attempt left behind, so every later call hands this back instead. +static INIT_FAILURE: OnceLock = OnceLock::new(); + byond_ffi_fn! { auxtools_init(_input) { - if get_init_level() == InitLevel::None { - return Some("SUCCESS".to_owned()) + if let Some(failure) = INIT_FAILURE.get() { + return Some(failure.clone()); } - let byondcore = match sigscan::Scanner::for_module(BYONDCORE) { - Some(v) => v, - None => return Some("FAILED (Couldn't create scanner for byondcore.dll)".to_owned()) - }; + match try_init() { + Ok(()) => Some("SUCCESS".to_owned()), + Err(e) => { + // a hook must not fire in a library that only got half set up + hooks::clear_hooks(); + proc::clear_procs(); + Some(INIT_FAILURE.get_or_init(|| format!("FAILED ({})", e)).clone()) + } + } +} } + +fn try_init() -> Result<(), String> { + if get_init_level() == InitLevel::None { + return Ok(()); + } let mut did_full = false; let mut did_partial = false; if get_init_level() == InitLevel::Full { did_full = true; - if let Err(e) = version::init() { - return Some(format!("FAILED ({})", e)); - } - - find_signatures! { byondcore, - (current_execution_context as *mut *mut raw_types::procs::ExecutionContext), - (suspended_procs as *mut raw_types::procs::SuspendedProcs), - (suspended_procs_buffer as *mut raw_types::procs::SuspendedProcsBuffer), - call_proc_by_id, - call_datum_proc_by_name, - get_proc_array_entry, - get_string_id, - get_variable, - set_variable, - get_string_table_entry, - inc_ref_count, - dec_ref_count, - get_assoc_element, - set_assoc_element, - create_list, - append_to_list, - remove_from_list, - get_length, - get_misc_by_id, - to_string, - runtime - } - - unsafe { - raw_types::funcs::CURRENT_EXECUTION_CONTEXT = current_execution_context; - raw_types::funcs::SUSPENDED_PROCS = suspended_procs; - raw_types::funcs::SUSPENDED_PROCS_BUFFER = suspended_procs_buffer; - raw_types::funcs::call_proc_by_id_byond = call_proc_by_id; - raw_types::funcs::call_datum_proc_by_name_byond = call_datum_proc_by_name; - raw_types::funcs::get_proc_array_entry_byond = get_proc_array_entry; - raw_types::funcs::get_string_id_byond = get_string_id; - raw_types::funcs::get_variable_byond = get_variable; - raw_types::funcs::set_variable_byond = set_variable; - raw_types::funcs::get_string_table_entry_byond = get_string_table_entry; - raw_types::funcs::inc_ref_count_byond = inc_ref_count; - raw_types::funcs::dec_ref_count_byond = dec_ref_count; - raw_types::funcs::get_assoc_element_byond = get_assoc_element; - raw_types::funcs::set_assoc_element_byond = set_assoc_element; - raw_types::funcs::create_list_byond = create_list; - raw_types::funcs::append_to_list_byond = append_to_list; - raw_types::funcs::remove_from_list_byond = remove_from_list; - raw_types::funcs::get_length_byond = get_length; - raw_types::funcs::get_misc_by_id_byond = get_misc_by_id; - raw_types::funcs::to_string_byond = to_string; - raw_types::funcs::runtime_byond = runtime; - } - - if pin_dll().is_err() { - return Some("FAILED (Could not pin the library in memory.)".to_owned()); - } - - if hooks::init().is_err() { - return Some("Failed (Couldn't initialize proc hooking)".to_owned()); - } + version::init()?; + symbols::resolve_full()?; + pin_dll().map_err(|()| "Could not pin the library in memory.".to_owned())?; + hooks::init().map_err(|_| "Couldn't initialize proc hooking".to_owned())?; set_init_level(InitLevel::Partial); } - if get_init_level() == InitLevel::Partial { did_partial = true; // This is a heap ptr so fetch it on partial loads - find_signature! { byondcore, variable_names as *mut VariableNameIdTable } - - unsafe { - raw_types::funcs::VARIABLE_NAMES = variable_names; - } + symbols::resolve_partial()?; proc::populate_procs(); for cthook in inventory::iter:: { - if let Err(e) = hooks::hook(cthook.proc_path, cthook.hook) { - return Some(format!("FAILED (Could not hook proc {}: {:?})", cthook.proc_path, e)); - } + hooks::hook(cthook.proc_path, cthook.hook).map_err(|e| format!("Could not hook proc {}: {:?}", cthook.proc_path, e))?; } set_init_level(InitLevel::None); } @@ -336,19 +142,15 @@ byond_ffi_fn! { auxtools_init(_input) { // Run user-defined initializers if did_full { - if let Err(err) = init::run_full_init() { - return Some(format!("FAILED ({})", err)); - } + init::run_full_init()?; } if did_partial { - if let Err(err) = init::run_partial_init() { - return Some(format!("FAILED ({})", err)); - } + init::run_partial_init()?; } - Some("SUCCESS".to_owned()) -} } + Ok(()) +} byond_ffi_fn! { auxtools_shutdown(_input) { if get_init_level() != InitLevel::None { @@ -415,22 +217,12 @@ byond_ffi_fn! { auxtools_full_shutdown(_input) { } } byond_ffi_fn! { auxtools_check_signatures(_input) { - let byondcore = match sigscan::Scanner::for_module(BYONDCORE) { - Some(v) => v, - None => return Some("FAILED (Couldn't create scanner for byondcore.dll)".to_owned()) - }; if let Err(e) = version::init() { return Some(format!("FAILED ({})", e)); } - let mut missing = Vec::<&'static str>::new(); - for (name, found) in SIGNATURES0.check_all(&byondcore) { - if !found { - missing.push(name); - } - } - if missing.is_empty() { - Some("SUCCESS".to_owned()) - } else { - Some(format!("MISSING: {}", missing.join(", "))) + match symbols::unresolved() { + Err(e) => Some(format!("FAILED ({})", e)), + Ok(missing) if missing.is_empty() => Some("SUCCESS".to_owned()), + Ok(missing) => Some(format!("MISSING: {}", missing.join(", "))) } } } diff --git a/auxtools/src/list.rs b/auxtools/src/list.rs index 9fdb3066..9453824f 100644 --- a/auxtools/src/list.rs +++ b/auxtools/src/list.rs @@ -3,7 +3,7 @@ use std::iter::FromIterator; use crate::*; /// A wrapper around [Values](struct.Value.html) that make working with lists a -/// little easier +/// little easier. It can also hold an `/alist`, where numbers are keys. pub struct List { value: Value } @@ -97,43 +97,65 @@ impl List { self.len() == 0 } - pub const fn is_list(value: &Value) -> bool { - matches!( - value.raw.tag, - raw_types::values::ValueTag::List - | raw_types::values::ValueTag::ArgList - | raw_types::values::ValueTag::MobContents - | raw_types::values::ValueTag::TurfContents - | raw_types::values::ValueTag::AreaContents - | raw_types::values::ValueTag::WorldContents - | raw_types::values::ValueTag::ObjContents - | raw_types::values::ValueTag::MobVars - | raw_types::values::ValueTag::ObjVars - | raw_types::values::ValueTag::TurfVars - | raw_types::values::ValueTag::AreaVars - | raw_types::values::ValueTag::ClientVars - | raw_types::values::ValueTag::Vars - | raw_types::values::ValueTag::MobOverlays - | raw_types::values::ValueTag::MobUnderlays - | raw_types::values::ValueTag::ObjOverlays - | raw_types::values::ValueTag::ObjUnderlays - | raw_types::values::ValueTag::TurfOverlays - | raw_types::values::ValueTag::TurfUnderlays - | raw_types::values::ValueTag::AreaOverlays - | raw_types::values::ValueTag::AreaUnderlays - | raw_types::values::ValueTag::ImageOverlays - | raw_types::values::ValueTag::ImageUnderlays - | raw_types::values::ValueTag::ImageVars - | raw_types::values::ValueTag::TurfVisContents - | raw_types::values::ValueTag::ObjVisContents - | raw_types::values::ValueTag::MobVisContents - | raw_types::values::ValueTag::ImageVisContents - | raw_types::values::ValueTag::TurfVisLocs - | raw_types::values::ValueTag::ObjVisLocs - | raw_types::values::ValueTag::MobVisLocs - | raw_types::values::ValueTag::WorldVars - | raw_types::values::ValueTag::GlobalVars - ) + /// Whether this is a DM `/alist`, where numbers are keys and not positions. + pub fn is_alist(&self) -> bool { + self.value.raw.tag == raw_types::values::ValueTag::Alist + } + + /// Every key and value of an `/alist`, in the order `for (var/k in A)` + /// gives them. + /// + /// This is a copy, so nothing here points into BYOND's tree once it + /// returns. + pub fn alist_pairs(&self) -> DMResult> { + if !self.is_alist() { + return Err(runtime!("attempted to read alist pairs of a non-alist")); + } + + let id = unsafe { self.value.raw.data.id } as usize; + let record = unsafe { + // raw reads of BYOND's table, so a stale id has to be turned away + // here + if id >= *raw_types::funcs::ALIST_TABLE_COUNT as usize { + return Err(runtime!("alist id {} is outside the alist table", id)); + } + // BYOND reallocates the table as it grows, so read the pointer + // every time + *(*raw_types::funcs::ALIST_TABLE).add(id) + }; + if record.is_null() { + return Err(runtime!("alist id {} has no record", id)); + } + + let mut pairs = vec![]; + let mut node = unsafe { (*record).root }; + let mut pending = vec![]; + // left, node, right, without recursing + while !node.is_null() || !pending.is_empty() { + while !node.is_null() { + pending.push(node); + node = unsafe { (*node).left }; + } + let entry = pending.pop().unwrap(); + unsafe { + pairs.push((Value::from_raw((*entry).key), Value::from_raw((*entry).value))); + node = (*entry).right; + } + } + + Ok(pairs) + } + + /// Whether DM's `islist()` says yes to this value. + /// + /// This asks BYOND instead of matching on the tag, because a tag can't + /// answer it: a `filters` list and a single filter share one. + pub fn is_list(value: &Value) -> bool { + let mut is_list: u8 = 0; + unsafe { + assert_eq!(raw_types::funcs::value_is_list(&mut is_list, value.raw), 1); + } + is_list != 0 } } diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index 63b10c7e..ba780f4c 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -1,7 +1,8 @@ use std::{ cell::RefCell, collections::{hash_map::Entry, HashMap}, - fmt + fmt, + marker::PhantomData }; use ahash::RandomState; @@ -26,18 +27,44 @@ use crate::*; // world << "!!!" // ``` // -// To get the nth override, use [get_proc_override]: `let hello = get_proc_override("/proc/hello", n).unwrap()` -// [get_proc] retrieves the base proc. +// To get the nth override, use [Proc::find_override]: `let hello = Proc::find_override("/proc/hello", n).unwrap()` +// [Proc::find] retrieves the base proc. /// Used to hook and call procs. +/// +/// A `Proc` stays on the thread BYOND runs on. Calling one from anywhere else +/// would run DM code while the game is in the middle of something else. +/// +/// ```compile_fail +/// fn assert_send() {} +/// assert_send::(); +/// ``` #[derive(Clone)] pub struct Proc { pub id: raw_types::procs::ProcId, - pub entry: *mut raw_types::procs::ProcEntry, - pub path: String + pub path: String, + // Keeps a Proc from being sent to another thread + phantom: PhantomData<*mut ()> +} + +fn entry_by_id(id: raw_types::procs::ProcId) -> *mut raw_types::procs::ProcEntry { + let mut entry: *mut raw_types::procs::ProcEntry = std::ptr::null_mut(); + unsafe { + assert_eq!(raw_types::funcs::get_proc_array_entry(&mut entry, id), 1); + } + entry } impl Proc { + /// BYOND's own record for this proc. + /// + /// Don't hold on to the pointer. BYOND moves its whole proc table whenever + /// the table grows (`new` on a verb path does it), so it is only good until + /// the next time DM code runs. + pub fn entry(&self) -> *mut raw_types::procs::ProcEntry { + entry_by_id(self.id) + } + /// Finds the first proc with the given path pub fn find>(path: S) -> Option { get_proc(path) @@ -49,18 +76,15 @@ impl Proc { } pub fn from_id(id: raw_types::procs::ProcId) -> Option { - let mut proc_entry: *mut raw_types::procs::ProcEntry = std::ptr::null_mut(); - unsafe { - assert_eq!(raw_types::funcs::get_proc_array_entry(&mut proc_entry, id), 1); - } + let proc_entry = entry_by_id(id); if proc_entry.is_null() { return None; } - let proc_name = strip_path(unsafe { StringRef::from_id((*proc_entry).path).into() }); + let path = strip_path(unsafe { StringRef::from_id((*proc_entry).path).into() }); Some(Proc { id, - entry: proc_entry, - path: proc_name.clone() + path, + phantom: PhantomData }) } @@ -80,14 +104,14 @@ impl Proc { pub fn parameter_names(&self) -> Vec { unsafe { - let (data, count) = raw_types::misc::get_parameters(*(*self.entry).metadata.parameters()); + let (data, count) = raw_types::misc::get_parameters(*(*self.entry()).metadata.parameters()); (0..count).map(|i| StringRef::from_variable_id((*data.add(i)).name)).collect() } } pub fn local_names(&self) -> Vec { unsafe { - let (names, count) = raw_types::misc::get_locals(*(*self.entry).metadata.locals()); + let (names, count) = raw_types::misc::get_locals(*(*self.entry()).metadata.locals()); (0..count).map(|i| StringRef::from_variable_id(*names.add(i))).collect() } } @@ -97,7 +121,7 @@ impl Proc { } pub unsafe fn bytecode_mut_ptr(&self) -> (*mut u32, u16) { - raw_types::misc::get_bytecode(*(*self.entry).metadata.bytecode()) + raw_types::misc::get_bytecode(*(*self.entry()).metadata.bytecode()) } pub unsafe fn bytecode(&self) -> &[u32] { @@ -131,7 +155,10 @@ impl Proc { let args: Vec<_> = args.iter().map(|e| e.raw).collect(); - if raw_types::funcs::call_proc_by_id(&mut ret, Value::NULL.raw, 0, self.id, 0, Value::NULL.raw, args.as_ptr(), args.len(), 0, 0) == 1 { + // 2 is the proc_type BYOND's own direct calls pass. It never passes + // 0, and a `..()` with no parent hands back garbage in + // a frame that has it. + if raw_types::funcs::call_proc_by_id(&mut ret, Value::NULL.raw, 2, self.id, 0, Value::NULL.raw, args.as_ptr(), args.len(), 0, 0) == 1 { return Ok(Value::from_raw_owned(ret)); } } @@ -149,7 +176,7 @@ impl Proc { impl fmt::Debug for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let path = unsafe { (*self.entry).path }; + let path = unsafe { (*self.entry()).path }; write!(f, "Proc({:?})", unsafe { StringRef::from_id(path) }) } } diff --git a/auxtools/src/raw_types/funcs.cpp b/auxtools/src/raw_types/funcs.cpp index 1b225092..37a8970f 100644 --- a/auxtools/src/raw_types/funcs.cpp +++ b/auxtools/src/raw_types/funcs.cpp @@ -52,9 +52,26 @@ struct RestoreJmpBuf #endif +// The REGPARM markers are real on Linux and expand to nothing on Windows, so a +// wrong one here is a Linux-only bug that no Windows test can catch. These were +// read off libbyond.so's function prologues on 516.1669 and 516.1687. +// +// How to check one: does the function read `eax` or `edx` before writing it in +// its first handful of instructions? If so it takes arguments in registers and +// needs a marker. If it opens by reading `[esp+arg_N]` it is plain cdecl and must +// not have one. The export table can't tell you, none of these are in it. +// +// Two things make this easy to get wrong: +// +// * A Value-returning regparm function spends `eax` on the hidden return +// buffer, so its first real argument starts at `edx`. Count the buffer. +// * The convention is per-function and per-build. `remove_from_list` changed +// from registers to the stack at 1674 with no change to its signature, +// while its identically-shaped sibling `append_to_list` did not move. Don't +// infer one row from another. extern "C" { - DEFINE_byond_REGPARM3(call_proc_by_id, Value, (Value, uint32_t, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); + DEFINE_byond(call_proc_by_id, Value, (Value, uint32_t, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); DEFINE_byond(call_datum_proc_by_name, Value, (Value, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); DEFINE_byond(get_proc_array_entry, void *, (uint32_t)); DEFINE_byond_REGPARM3(get_string_id, uint32_t, (const char *, uint8_t, uint8_t, uint8_t)); @@ -63,34 +80,75 @@ extern "C" DEFINE_byond(get_string_table_entry, void *, (uint32_t)); DEFINE_byond(inc_ref_count, void, (Value)); DEFINE_byond(dec_ref_count, void, (Value)); - DEFINE_byond_REGPARM3(get_list_by_id, void *, (uint32_t)); - DEFINE_byond_REGPARM3(get_assoc_element, Value, (Value, Value)); - DEFINE_byond_REGPARM3(set_assoc_element, void, (Value, Value, Value)); + DEFINE_byond(get_assoc_element, Value, (Value, Value)); + DEFINE_byond(set_assoc_element, void, (Value, Value, Value)); DEFINE_byond(create_list, uint32_t, (uint32_t)); DEFINE_byond_REGPARM2(append_to_list, void, (Value, Value)); - DEFINE_byond_REGPARM2(remove_from_list, void, (Value, Value)); + // The one function known to have changed convention mid-range, so it gets + // both declarations. Linux builds up to 516.1673 take the list in `eax:edx`, + // and 516.1674 on take everything on the stack. Rust sets the flag from the + // running build, and the `remove_from_list` wrapper below picks the matching + // pointer type. + DEFINE_byond(remove_from_list, bool, (Value, Value)); + using Fnremove_from_list_regparm_byond = bool(LINUX_REGPARM2 *)(Value, Value); + bool remove_from_list_in_registers = false; + // The one function here whose *return* differs by platform, not just its + // argument placement. Windows hands the count straight back in `eax`. Linux + // returns a DM Value through a caller-supplied buffer, so the count has to be + // unpacked out of it. See the `get_length` wrapper below. +#ifdef _WIN32 DEFINE_byond(get_length, uint32_t, (Value)); +#else + DEFINE_byond(get_length, Value, (Value)); +#endif + // BYOND's own `islist()`. The last argument says whether a pointer to a list + // counts as a list. + DEFINE_byond(value_is_list, bool, (Value, uint8_t)); DEFINE_byond(get_misc_by_id, void *, (uint32_t)); DEFINE_byond(to_string, uint32_t, (Value)); } +#ifndef _WIN32 +// Unpack the DM Value Linux's `get_length` returns into the plain count every +// caller here wants. Tag 0x2A is Number and its data half is an `f32`. Anything +// else means the argument had no length, which BYOND itself reports as 0. +// +// The tag is masked to a byte on purpose: BYOND leaves the three padding bytes +// above a tag indeterminate, so comparing the whole dword answers wrong at +// random. +static uint32_t length_value_to_count(Value length) +{ + if ((length.type & 0xFF) != 0x2A) + { + return 0; + } + float count; + __builtin_memcpy(&count, &length.value, sizeof(count)); + if (!(count > 0.0f)) + { + return 0; + } + return static_cast(count); +} +#endif + extern "C" uint8_t call_proc_by_id( Value *out, Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, const Value *args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2) + uint32_t args_count, + uint32_t callback, + uint32_t callback_value) { RuntimeContext ctx(false); BYOND_TRY { - *out = call_proc_by_id_byond(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + *out = call_proc_by_id_byond(usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value); return 1; } BYOND_CATCH @@ -106,21 +164,27 @@ extern "C" uint8_t call_datum_proc_by_name( uint32_t proc_name, Value src, Value *args, - uint8_t args_count, - uint32_t unk_0, - uint32_t unk_1) + uint32_t args_count, + uint32_t callback, + uint32_t callback_value) { - RuntimeContext ctx(false); + // Intercepts, unlike `call_proc_by_id` above. The errors this function raises + // itself (an unknown proc name, a null receiver) happen before any DM code + // runs, and with `false` BYOND's own exception flew straight past the catch and + // killed the process at the first Rust frame. The proc it ends up calling still + // reports its own errors normally: `call_proc_by_id_hook_trampoline` pushes + // `false` around every DM call. + RuntimeContext ctx(true); BYOND_TRY { clean(usr); clean(src); - for (int i = 0; i < args_count; i++) + for (uint32_t i = 0; i < args_count; i++) { clean(args[i]); } - *out = call_datum_proc_by_name_byond(usr, proc_type, proc_name, src, args, args_count, unk_0, unk_1); + *out = call_datum_proc_by_name_byond(usr, proc_type, proc_name, src, args, args_count, callback, callback_value); return 1; } BYOND_CATCH @@ -239,21 +303,6 @@ extern "C" uint8_t dec_ref_count(Value value) } } -extern "C" uint8_t get_list_by_id(void **out, uint32_t list_id) -{ - RuntimeContext ctx(true); - - BYOND_TRY - { - *out = get_list_by_id_byond(list_id); - return 1; - } - BYOND_CATCH - { - return 0; - } -} - extern "C" uint8_t get_assoc_element(Value *out, Value datum, Value index) { RuntimeContext ctx(true); @@ -329,7 +378,14 @@ extern "C" uint8_t remove_from_list(Value list, Value value) { clean(list); clean(value); - remove_from_list_byond(list, value); + if (remove_from_list_in_registers) + { + reinterpret_cast(remove_from_list_byond)(list, value); + } + else + { + remove_from_list_byond(list, value); + } return 1; } BYOND_CATCH @@ -345,7 +401,28 @@ extern "C" uint8_t get_length(uint32_t *out, Value value) BYOND_TRY { clean(value); +#ifdef _WIN32 *out = get_length_byond(value); +#else + *out = length_value_to_count(get_length_byond(value)); +#endif + return 1; + } + BYOND_CATCH + { + return 0; + } +} + +extern "C" uint8_t value_is_list(uint8_t *out, Value value) +{ + RuntimeContext ctx(true); + + BYOND_TRY + { + clean(value); + // 0 is what the `islist()` opcode passes, so a pointer to a list is not a list + *out = value_is_list_byond(value, 0); return 1; } BYOND_CATCH diff --git a/auxtools/src/raw_types/funcs.rs b/auxtools/src/raw_types/funcs.rs index 6f1d58e5..59022fe2 100644 --- a/auxtools/src/raw_types/funcs.rs +++ b/auxtools/src/raw_types/funcs.rs @@ -7,6 +7,11 @@ pub static mut CURRENT_EXECUTION_CONTEXT: *mut *mut procs::ExecutionContext = st pub static mut SUSPENDED_PROCS_BUFFER: *mut procs::SuspendedProcsBuffer = std::ptr::null_mut(); pub static mut SUSPENDED_PROCS: *mut procs::SuspendedProcs = std::ptr::null_mut(); +// BYOND's globals for the alist table: a pointer to the table, and its length. +// The table holds one record pointer per alist id, null for a free slot. +pub static mut ALIST_TABLE: *const *const *const lists::Alist = std::ptr::null(); +pub static mut ALIST_TABLE_COUNT: *const u32 = std::ptr::null(); + pub static mut VARIABLE_NAMES: *const variables::VariableNameIdTable = std::ptr::null(); // Function pointers exported by C++ but set by Rust @@ -27,7 +32,9 @@ extern "C" { pub static mut create_list_byond: *const c_void; pub static mut append_to_list_byond: *const c_void; pub static mut remove_from_list_byond: *const c_void; + pub static mut remove_from_list_in_registers: bool; pub static mut get_length_byond: *const c_void; + pub static mut value_is_list_byond: *const c_void; pub static mut get_misc_by_id_byond: *const c_void; pub static mut to_string_byond: *const c_void; pub static mut runtime_byond: *const c_void; @@ -40,12 +47,12 @@ extern "C" { usr: values::Value, proc_type: u32, proc_id: procs::ProcId, - unk_0: u32, + override_depth: u32, src: values::Value, args: *const values::Value, args_count_l: usize, - unk_1: u32, - unk_2: u32 + callback: u32, + callback_value: u32 ) -> u8; pub fn call_datum_proc_by_name( out: *mut values::Value, @@ -55,8 +62,8 @@ extern "C" { src: values::Value, args: *mut values::Value, args_count_l: usize, - unk_0: u32, - unk_1: u32 + callback: u32, + callback_value: u32 ) -> u8; pub fn get_proc_array_entry(out: *mut *mut procs::ProcEntry, id: procs::ProcId) -> u8; pub fn get_string_id(out: *mut strings::StringId, string: *const c_char) -> u8; @@ -71,6 +78,7 @@ extern "C" { pub fn append_to_list(list: values::Value, value: values::Value) -> u8; pub fn remove_from_list(list: values::Value, value: values::Value) -> u8; pub fn get_length(out: *mut u32, value: values::Value) -> u8; + pub fn value_is_list(out: *mut u8, value: values::Value) -> u8; pub fn get_misc_by_id(out: *mut *mut c_void, index: misc::MiscId) -> u8; pub fn to_string(out: *mut strings::StringId, value: values::Value) -> u8; } diff --git a/auxtools/src/raw_types/lists.rs b/auxtools/src/raw_types/lists.rs index 38b0c875..272dfbd2 100644 --- a/auxtools/src/raw_types/lists.rs +++ b/auxtools/src/raw_types/lists.rs @@ -4,20 +4,31 @@ use super::values; #[derive(Copy, Clone, Debug)] pub struct ListId(pub u32); +/// One key->value pair of an associative list, as a red-black tree node. +/// +/// 32 bytes. Matches BYOND's own tree lookup (516.1669), which reads the key at +/// `+0` and the children at `+16` and `+20`. This used to declare `color` at +/// `+16`, which put both child pointers 4 bytes past where BYOND writes them. +/// Nothing read the children, so it never showed. #[repr(C)] -#[allow(unused)] -enum Color { - Red = 0, - Black = 1 +pub struct AssociativeListEntry { + /// Non-owning; the same ref also occupies a `vector_part` slot. + pub key: values::Value, + /// Owning; released when the tree is torn down. + pub value: values::Value, + pub left: *mut AssociativeListEntry, + pub right: *mut AssociativeListEntry, + pub parent: *mut AssociativeListEntry, + /// 0 = red, 1 = black. + pub color: u8 } +/// The 8-byte record an `/alist` id points at in the alist table. #[repr(C)] -pub struct AssociativeListEntry { - key: values::Value, - value: values::Value, - color: Color, - left: *mut AssociativeListEntry, - right: *mut AssociativeListEntry +pub struct Alist { + pub root: *mut AssociativeListEntry, + /// A new alist starts at 1, not 0. + pub refcount: u32 } #[repr(C)] diff --git a/auxtools/src/raw_types/misc.rs b/auxtools/src/raw_types/misc.rs index f6509f77..00324e22 100644 --- a/auxtools/src/raw_types/misc.rs +++ b/auxtools/src/raw_types/misc.rs @@ -138,6 +138,7 @@ pub fn set_bytecode(id: BytecodeId, new_bytecode: *mut u32, new_bytecode_count: (*misc).bytecode.bytecode = new_bytecode; (*misc).bytecode.count = new_bytecode_count; } + return; } let misc = misc as *mut Misc_V1; diff --git a/auxtools/src/raw_types/procs.rs b/auxtools/src/raw_types/procs.rs index 6e915518..0f25846f 100644 --- a/auxtools/src/raw_types/procs.rs +++ b/auxtools/src/raw_types/procs.rs @@ -13,8 +13,9 @@ pub struct ProcEntry { pub name: strings::StringId, pub desc: strings::StringId, pub category: strings::StringId, + // the last `/` part of `path` + path_basename: strings::StringId, flags: u32, - unk_1: u32, pub metadata: ProcMetadata } @@ -24,24 +25,35 @@ pub struct ProcEntry { )] #[repr(C)] pub struct ProcMetadata { + // three bytes straight out of the .dmb, meaning unknown #[only_in(Post1630)] - unk_2: u32, + byte_24: i8, + #[only_in(Post1630)] + byte_25: u8, + #[only_in(Post1630)] + byte_26: i8, pub bytecode: misc::BytecodeId, pub locals: misc::LocalsId, - pub parameters: misc::ParametersId + pub parameters: misc::ParametersId, + // the proc `..()` goes to, 0xFFFF for none. Brings the entry to the 44 bytes + // BYOND strides its proc table by + #[only_in(Post1630)] + next_override: ProcId } #[repr(C)] pub struct ProcInstance { pub proc: ProcId, + /// The `proc_type` this frame was called with. `..()` reads it back. pub flags: u8, + /// Which override of the proc this frame runs, 0 for the most derived. + pub override_depth: u8, pub mega_hack: u16, pub usr: values::Value, pub src: values::Value, pub context: *mut ExecutionContext, pub argslist_idx: values::ValueData, unk_1: u32, - unk_2: u32, inner: ProcInstanceInner } @@ -65,19 +77,33 @@ impl ProcInstance { )] #[repr(C)] struct ProcInstanceInner { + #[only_in(Pre516)] + unk_2: u32, + // called with the result when the proc ends, null for none #[only_in(Post516)] - unk_pre: u32, + callback: u32, + #[only_in(Post516)] + callback_value: u32, pub args_count: u32, pub args: *mut values::Value, - #[only_in(Pre516)] - unk_3: [u8; 0x58], - #[only_in(Post516)] - unk_4: [u8; 0x58], + // room for ten values, shared by the args and the locals, so a small proc + // doesn't have to allocate + arg_local_store: [u8; 0x50], + internal_arg_count: u32, + // not 0 once the store has overflowed onto the heap + external_arg_count: u32, pub time_to_resume: u32 } +// Linux is 148 bytes against Windows' 152: from `cache` onward the fields sit 4 +// bytes earlier, which is the same shape Windows had before 1668. So Linux +// always takes the Pre1668 variant. +// +// The whole difference is the call flags. Windows from 1668 keeps them in a +// dword of their own, everything else packs them into the byte after +// `test_flag`. #[versioned( - Pre1668 if crate::version::BYOND_VERSION_MAJOR <= 515 || (crate::version::BYOND_VERSION_MAJOR == 516 && crate::version::BYOND_VERSION_MINOR <= 1667), + Pre1668 if cfg!(unix) || crate::version::BYOND_VERSION_MAJOR <= 515 || (crate::version::BYOND_VERSION_MAJOR == 516 && crate::version::BYOND_VERSION_MINOR <= 1667), Post1668, )] #[repr(C)] @@ -89,32 +115,42 @@ pub struct ExecutionContext { pub bytecode: *mut u32, pub bytecode_offset: u16, pub test_flag: u8, - unk_0: u8, - cached_datum: values::Value, #[only_in(Pre1668)] - unk_1_pre: [u8; 0x10], + exec_call_flags: u8, #[only_in(Post1668)] - unk_1_post: [u8; 0x14], + exec_call_flags: u32, + cache: values::Value, + cache_key: values::Value, + // two pointers into exec_proc's own stack frame + scratch_ptr_a: u32, + scratch_ptr_b: u32, pub dot: values::Value, pub locals: *mut values::Value, pub stack: *mut values::Value, pub locals_count: u16, pub stack_size: u16, - unk_2: u32, + // the iterators of the loops this one is nested in + iterator_stack: u32, pub current_iterator: *mut values::Value, pub iterator_allocated: u32, pub iterator_length: u32, pub iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - pub iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, + pub iterator_filter_type: values::Value, + pub iterator_filter_bitflags: u32, + pub iterator_kind: u8, + // byond-re calls this one `jnz_lchk_loop_count` pub infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] + // 0 while the proc is sleeping. There is no separate "paused" byte + not_suspended: u8, + caller_chain_preserve_depth: u8, + background: u8, + exception_state: u8, + throw_handler: u32, + // timevals. The last is the deadline a background proc's loops run against + time_now: [u32; 2], + cpu_clock_init: [u32; 2], + cpu_clock_quantum: [u32; 2], + loop_timeout: [u32; 2] } #[repr(C)] @@ -122,6 +158,7 @@ pub struct SuspendedProcsBuffer { pub buffer: *mut *mut ProcInstance } +#[cfg(windows)] #[repr(C)] pub struct SuspendedProcs { pub front: usize, @@ -129,184 +166,73 @@ pub struct SuspendedProcs { pub capacity: usize } +// On Linux the compiler lays the two index globals out the other way round, +// `back` first. Whatever follows them isn't read. +#[cfg(unix)] +#[repr(C)] +pub struct SuspendedProcs { + pub back: usize, + pub front: usize +} + #[cfg(test)] mod layout_tests { use std::mem::{offset_of, size_of}; - use super::{misc, strings, values}; - - // Reference structs: verbatim copies of the original hand-written variants, - // kept here so that any accidental layout change in the macro output is caught. - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcMetadataPre1630 { - bytecode: misc::BytecodeId, - locals: misc::LocalsId, - parameters: misc::ParametersId - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcMetadataPost1630 { - unk_2: u32, - bytecode: misc::BytecodeId, - locals: misc::LocalsId, - parameters: misc::ParametersId - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcInstanceInnerPre516 { - args_count: u32, - args: *mut values::Value, - unk_3: [u8; 0x58], - time_to_resume: u32 - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcInstanceInnerPost516 { - unk_3: u32, - args_count: u32, - args: *mut values::Value, - unk_4: [u8; 0x58], - time_to_resume: u32 - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefExecutionContextPre1668 { - proc_instance: *mut super::ProcInstance, - parent_context: *mut super::ExecutionContext, - filename: strings::StringId, - line: u32, - bytecode: *mut u32, - bytecode_offset: u16, - test_flag: u8, - unk_0: u8, - cached_datum: values::Value, - unk_1: [u8; 0x10], - dot: values::Value, - locals: *mut values::Value, - stack: *mut values::Value, - locals_count: u16, - stack_size: u16, - unk_2: u32, - current_iterator: *mut values::Value, - iterator_allocated: u32, - iterator_length: u32, - iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, - infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefExecutionContextPost1668 { - proc_instance: *mut super::ProcInstance, - parent_context: *mut super::ExecutionContext, - filename: strings::StringId, - line: u32, - bytecode: *mut u32, - bytecode_offset: u16, - test_flag: u8, - unk_0: u8, - cached_datum: values::Value, - unk_1: [u8; 0x14], - dot: values::Value, - locals: *mut values::Value, - stack: *mut values::Value, - locals_count: u16, - stack_size: u16, - unk_2: u32, - current_iterator: *mut values::Value, - iterator_allocated: u32, - iterator_length: u32, - iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, - infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] - } - - #[test] - fn proc_metadata_pre1630() { - type Gen = super::ProcMetadataPre1630; - type Ref = RefProcMetadataPre1630; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, parameters), offset_of!(Ref, parameters)); - } - - #[test] - fn proc_metadata_post1630() { - type Gen = super::ProcMetadataPost1630; - type Ref = RefProcMetadataPost1630; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, parameters), offset_of!(Ref, parameters)); - } + // The numbers below are read off the binaries, 516.1688: what BYOND + // allocates for each struct and where its own code reaches into them. + // The ones for older builds are from byond-re's notes. #[test] - fn proc_instance_inner_pre516() { - type Gen = super::ProcInstanceInnerPre516; - type Ref = RefProcInstanceInnerPre516; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, args_count), offset_of!(Ref, args_count)); - assert_eq!(offset_of!(Gen, args), offset_of!(Ref, args)); - assert_eq!(offset_of!(Gen, time_to_resume), offset_of!(Ref, time_to_resume)); + fn execution_context_linux_and_old_windows() { + type Gen = super::ExecutionContextPre1668; + assert_eq!(size_of::(), 0x94); + assert_eq!(offset_of!(Gen, bytecode_offset), 0x14); + assert_eq!(offset_of!(Gen, dot), 0x30); + assert_eq!(offset_of!(Gen, locals), 0x38); + assert_eq!(offset_of!(Gen, stack_size), 0x42); + assert_eq!(offset_of!(Gen, current_iterator), 0x48); + assert_eq!(offset_of!(Gen, iterator_kind), 0x64); + assert_eq!(offset_of!(Gen, infinite_loop_count), 0x68); } #[test] - fn proc_instance_inner_post516() { - type Gen = super::ProcInstanceInnerPost516; - type Ref = RefProcInstanceInnerPost516; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, args_count), offset_of!(Ref, args_count)); - assert_eq!(offset_of!(Gen, args), offset_of!(Ref, args)); - assert_eq!(offset_of!(Gen, time_to_resume), offset_of!(Ref, time_to_resume)); + fn execution_context_windows() { + type Gen = super::ExecutionContextPost1668; + assert_eq!(size_of::(), 0x98); + assert_eq!(offset_of!(Gen, bytecode_offset), 0x14); + assert_eq!(offset_of!(Gen, dot), 0x34); + assert_eq!(offset_of!(Gen, locals), 0x3C); + assert_eq!(offset_of!(Gen, stack_size), 0x46); + assert_eq!(offset_of!(Gen, current_iterator), 0x4C); + assert_eq!(offset_of!(Gen, iterator_kind), 0x68); + assert_eq!(offset_of!(Gen, infinite_loop_count), 0x6C); } #[test] - fn execution_context_pre1668() { - type Gen = super::ExecutionContextPre1668; - type Ref = RefExecutionContextPre1668; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, proc_instance), offset_of!(Ref, proc_instance)); - assert_eq!(offset_of!(Gen, filename), offset_of!(Ref, filename)); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, dot), offset_of!(Ref, dot)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, locals_count), offset_of!(Ref, locals_count)); + fn proc_instance() { + type Inner = super::ProcInstanceInnerPost516; + let inner = offset_of!(super::ProcInstance, inner); + assert_eq!(size_of::(), 0x90); + assert_eq!(offset_of!(super::ProcInstance, override_depth), 0x05); + assert_eq!(offset_of!(super::ProcInstance, context), 0x18); + assert_eq!(inner + offset_of!(Inner, callback), 0x24); + assert_eq!(inner + offset_of!(Inner, args_count), 0x2C); + assert_eq!(inner + offset_of!(Inner, args), 0x30); + assert_eq!(inner + offset_of!(Inner, external_arg_count), 0x88); + assert_eq!(inner + offset_of!(Inner, time_to_resume), 0x8C); + // 515 and older have one dword less in front of the args + assert_eq!(inner + offset_of!(super::ProcInstanceInnerPre516, args_count), 0x28); } #[test] - fn execution_context_post1668() { - type Gen = super::ExecutionContextPost1668; - type Ref = RefExecutionContextPost1668; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, proc_instance), offset_of!(Ref, proc_instance)); - assert_eq!(offset_of!(Gen, filename), offset_of!(Ref, filename)); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, dot), offset_of!(Ref, dot)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, locals_count), offset_of!(Ref, locals_count)); + fn proc_entry() { + let metadata = offset_of!(super::ProcEntry, metadata); + assert_eq!(size_of::(), 44); + assert_eq!(metadata + offset_of!(super::ProcMetadataPost1630, bytecode), 28); + assert_eq!(metadata + offset_of!(super::ProcMetadataPost1630, next_override), 40); + // before 1630 the entry is 36 bytes with the bytecode id at +24 + assert_eq!(metadata + size_of::(), 36); + assert_eq!(metadata + offset_of!(super::ProcMetadataPre1630, bytecode), 24); } } diff --git a/auxtools/src/raw_types/strings.rs b/auxtools/src/raw_types/strings.rs index a2a890c7..0097e3cc 100644 --- a/auxtools/src/raw_types/strings.rs +++ b/auxtools/src/raw_types/strings.rs @@ -23,5 +23,8 @@ pub struct StringEntry { pub right: *mut StringEntry, pub ref_count: u32, pub unk_1: u32, - pub unk_2: u32 + pub unk_2: u32, + pub encoding: u8, + /// Set for proc paths. BYOND never frees a string that has it. + pub is_proc_name: u8 } diff --git a/auxtools/src/raw_types/values.rs b/auxtools/src/raw_types/values.rs index 32837cc5..f66da363 100644 --- a/auxtools/src/raw_types/values.rs +++ b/auxtools/src/raw_types/values.rs @@ -43,18 +43,33 @@ value_tags! { // Lists List = 0x0F, ArgList = 0x10, + MobVerbs = 0x11, + ObjVerbs = 0x12, + TurfVerbs = 0x13, + AreaVerbs = 0x14, + ClientVerbs = 0x15, + SaveFileDir = 0x16, MobContents = 0x17, TurfContents = 0x18, AreaContents = 0x19, WorldContents = 0x1A, + MobGroup = 0x1B, ObjContents = 0x1C, DatumTypepath = 0x20, Datum = 0x21, SaveFile = 0x23, + SaveFileTypepath = 0x24, ProcRef = 0x26, File = 0x27, + // what `var/list/L = new()` pushes as its type. islist() says no to it + ListTypepath = 0x28, + // a typepath plus var overrides, applied after `new`. extools' name; the + // binary never names it + Prefab = 0x29, Number = 0x2A, + // what `F["key"]` evaluates to: a savefile id plus the key's string id + SaveFileEntry = 0x2B, MobVars = 0x2C, ObjVars = 0x2D, TurfVars = 0x2E, @@ -70,10 +85,34 @@ value_tags! { AreaOverlays = 0x38, AreaUnderlays = 0x39, Appearance = 0x3A, + ClientTypepath = 0x3B, Pointer = 0x3C, + // internal: the recorder `optimize_obj_type_init_procs` writes var inits + // into. DM code never sees it + InitVarRecorder = 0x3D, + // internal: a name constant that only DM-compiler code builds + CompilerNameConstant = 0x3E, + // also what an appearance's `type` comes back as + ImageTypepath = 0x3F, ImageOverlays = 0x40, ImageUnderlays = 0x41, ImageVars = 0x42, + ImageVerbs = 0x43, + ImageContents = 0x44, + // What `load_ext()` hands back: a reference to a resolved function in an + // external library. It is really any native-object handle: /icon and /regex + // objects are this tag too, told apart by a kind at `+8`. + ExtFunc = 0x45, + ClientImages = 0x46, + ClientScreen = 0x47, + // BYOND's own inc/dec_ref_count do nothing with this one + SaveFileVars = 0x48, + // the vars of a /list or /alist. The data is a string id, so it is + // refcounted like a string + ListVars = 0x49, + // internal, and no DM-visible name anywhere: an entry in a per-client + // resource set + Unnamed4A = 0x4A, TurfVisContents = 0x4B, ObjVisContents = 0x4C, MobVisContents = 0x4D, @@ -87,7 +126,9 @@ value_tags! { Alist = 0x55, PixLoc = 0x56, Vector = 0x57, - Callee = 0x58 + Callee = 0x58, + // the typepath of the 516 builtins: /alist, /pixloc, /vector, /callee + BuiltinTypepath = 0x59 } impl fmt::Display for Value { diff --git a/auxtools/src/raw_types/variables.rs b/auxtools/src/raw_types/variables.rs index 3cfa256b..84a9fd82 100644 --- a/auxtools/src/raw_types/variables.rs +++ b/auxtools/src/raw_types/variables.rs @@ -1,7 +1,20 @@ use super::strings; +// Not one BYOND struct, just two globals that sit next to each other: the name +// array and how many entries it has. `symbols` finds `entries` and backs up to +// wherever this starts. +#[cfg(windows)] #[repr(C)] pub struct VariableNameIdTable { pub entries: *const strings::StringId, pub count: u32 } + +// On Linux the count comes first. What follows `entries` there is the pointer +// to the global values, which is not a count of anything. +#[cfg(unix)] +#[repr(C)] +pub struct VariableNameIdTable { + pub count: u32, + pub entries: *const strings::StringId +} diff --git a/auxtools/src/sigscan.rs b/auxtools/src/sigscan.rs deleted file mode 100644 index c74182b1..00000000 --- a/auxtools/src/sigscan.rs +++ /dev/null @@ -1,211 +0,0 @@ -#[cfg(unix)] -mod linux; -#[cfg(windows)] -mod windows; - -use std::ops::{Bound, RangeBounds}; - -pub use auxtools_impl::convert_signature; -#[cfg(unix)] -pub use linux::Scanner; -pub use once_cell; -#[cfg(windows)] -pub use windows::Scanner; - -#[macro_export] -macro_rules! signature { - ($sig:tt) => { - $crate::sigscan::convert_signature!($sig) - }; -} - -#[macro_export] -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*)); -} - -#[macro_export] -macro_rules! signatures { - ( $( $name:ident => $sig:expr ),*) => { - struct Signatures { - $( pub $name: $crate::sigscan::SignatureMap, )* - } - - impl Signatures { - #[allow(dead_code)] - pub fn check_all(&self, scanner: &$crate::sigscan::Scanner) -> [(&'static str, bool); count!($($name)*)] { - let version = $crate::version::get().1; - [$( - (stringify!($name), self.$name.find(scanner, version).is_some()), - )*] - } - } - - static SIGNATURES0: $crate::sigscan::once_cell::sync::Lazy = $crate::sigscan::once_cell::sync::Lazy::new(|| Signatures { - $( $name: $sig, )* - }); - }; -} - -#[macro_export] -macro_rules! signature_struct { - (call, $sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::OffsetByCall, - bytes: signature!($sig) - } - }; - ($offset:literal, $sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::OffsetByInt($offset), - bytes: signature!($sig) - } - }; - (($spec:tt, $sig:tt)) => { - signature_struct!($spec, $sig) - }; - ($sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::NoOffset, - bytes: signature!($sig) - } - }; -} - -#[macro_export] -macro_rules! version_dependent_signature { - ( $($range:expr => $sig:tt),* ) => { - $crate::sigscan::SignatureMap::VersionDependent(vec![ - $(((std::ops::RangeBounds::start_bound(&$range), std::ops::RangeBounds::end_bound(&$range)), signature_struct!($sig)),)* - ]) - }; -} - -#[macro_export] -macro_rules! universal_signature { - (call, $sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!(call, $sig)) - }; - ($offset:literal, $sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!($offset, $sig)) - }; - ($sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!($sig)) - }; -} - -#[macro_export] -macro_rules! find_signature_inner { - ($scanner:ident, $name:ident, $type:ty) => { - let $name: $type; - if let Some(ptr) = SIGNATURES0.$name.find(&$scanner, $crate::version::get().1) { - $name = ptr as $type; - } else { - return Some(format!("FAILED (Couldn't find {})", stringify!($name))); - } - }; -} - -#[macro_export] -macro_rules! find_signature_inner_result { - ($scanner:ident, $name:ident, $type:ty) => { - let $name: $type; - if let Some(ptr) = SIGNATURES0.$name.find(&$scanner, $crate::version::get().1) { - $name = ptr as $type; - } else { - return Err(format!("FAILED (Couldn't find {})", stringify!($name))); - } - }; -} - -#[macro_export] -macro_rules! find_signature { - ($scanner:ident, $name:ident as $type:ty) => { - find_signature_inner!($scanner, $name, $type); - }; - - ($scanner:ident,($name:ident as $type:ty)) => { - find_signature_inner!($scanner, $name, $type); - }; - - ($scanner:ident, $name:ident) => { - find_signature_inner!($scanner, $name, *const c_void); - }; -} - -#[macro_export] -macro_rules! find_signature_result { - ($scanner:ident, $name:ident as $type:ty) => { - find_signature_inner_result!($scanner, $name, $type); - }; - - ($scanner:ident,($name:ident as $type:ty)) => { - find_signature_inner_result!($scanner, $name, $type); - }; - - ($scanner:ident, $name:ident) => { - find_signature_inner_result!($scanner, $name, *const c_void); - }; -} - -#[macro_export] -macro_rules! find_signatures { - ($scanner:ident, $($sig:tt),* ) => { - $( - find_signature!($scanner, $sig); - )* - }; -} - -#[macro_export] -macro_rules! find_signatures_result { - ($scanner:ident, $($sig:tt),* ) => { - $( - find_signature_result!($scanner, $sig); - )* - }; -} - -pub enum SignatureTreatment { - NoOffset, - OffsetByInt(isize), - OffsetByCall -} - -pub struct Signature { - pub treatment: SignatureTreatment, - pub bytes: &'static [Option] -} - -impl Signature { - pub fn find(&self, scanner: &Scanner) -> Option<*const std::ffi::c_void> { - scanner.find(self.bytes).map(|address| unsafe { - match self.treatment { - SignatureTreatment::NoOffset | SignatureTreatment::OffsetByInt(0) => address as *const std::ffi::c_void, - SignatureTreatment::OffsetByInt(i) => (address.offset(i) as *const *const std::ffi::c_void).read_unaligned(), - SignatureTreatment::OffsetByCall => { - let offset = (address.offset(1) as *const isize).read_unaligned(); - address.offset(5).offset(offset) as *const () as *const std::ffi::c_void - } - } - }) - } -} - -pub enum SignatureMap { - AllVersions(Signature), - VersionDependent(Vec<((Bound<&'static u32>, Bound<&'static u32>), Signature)>) -} - -impl SignatureMap { - pub fn find(&self, scanner: &Scanner, version: u32) -> Option<*const std::ffi::c_void> { - match self { - Self::AllVersions(signature) => signature.find(scanner), - Self::VersionDependent(map) => map - .iter() - .find(|(version_range, _)| version_range.contains(&version)) - .and_then(|(_, signature)| signature.find(scanner)) - } - } -} diff --git a/auxtools/src/sigscan/linux.rs b/auxtools/src/sigscan/linux.rs deleted file mode 100644 index 3446c0ec..00000000 --- a/auxtools/src/sigscan/linux.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{ - ffi::{c_void, CStr, CString}, - os::raw::{c_char, c_int} -}; - -use libc::{dl_iterate_phdr, dl_phdr_info, Elf32_Phdr, PT_LOAD}; - -#[repr(C)] -struct CallbackData { - module_name_ptr: *const c_char, - memory_start: usize, - memory_len: usize, - memory_area: Option<&'static [u8]> -} - -pub struct Scanner { - module_name: String -} - -extern "C" fn dl_phdr_callback(info: *mut dl_phdr_info, _size: usize, data: *mut c_void) -> c_int { - let info = unsafe { *info }; - let module_name = unsafe { CStr::from_ptr(info.dlpi_name) }.to_str().unwrap(); - let cb_data = unsafe { &mut *(data as *mut CallbackData) }; - let target_module_name = unsafe { CStr::from_ptr(cb_data.module_name_ptr as *mut c_char) }.to_str().unwrap(); - if !module_name.ends_with(target_module_name) { - return 0; - } - - let headers: &'static [Elf32_Phdr] = unsafe { std::slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize) }; - let elf_header = headers.iter().filter(|p| p.p_type == PT_LOAD).next().unwrap(); - - let start = (info.dlpi_addr + elf_header.p_vaddr) as usize; - let end = start + elf_header.p_memsz as usize; - let len = end - start; - - cb_data.memory_start = start; - cb_data.memory_len = len; - cb_data.memory_area = Some(unsafe { std::slice::from_raw_parts(start as *const u8, len) }); - 0 -} - -impl Scanner { - pub fn for_module(name: &str) -> Option { - Some(Scanner { - module_name: name.to_string() - }) - } - - pub fn find(&self, signature: &[Option]) -> Option<*mut u8> { - let module_name = CString::new(self.module_name.clone()).unwrap(); - let module_name_ptr = module_name.as_ptr(); - let mut data = CallbackData { - module_name_ptr, - memory_start: 0, - memory_len: 0, - memory_area: None - }; - unsafe { dl_iterate_phdr(Some(dl_phdr_callback), &mut data as *mut CallbackData as *mut c_void) }; - - let mut data_current = data.memory_start as *mut u8; - let data_end = (data.memory_start + data.memory_len) as *mut u8; - - if data_current.is_null() || data_end == data_current { - // There's no more bytes to scan or the module wasn't found. - return None; - } - - let mut signature_offset = 0; - let mut result: Option<*mut u8> = None; - - unsafe { - while data_current <= data_end { - if signature[signature_offset] == None || signature[signature_offset] == Some(*data_current) { - if signature.len() <= signature_offset + 1 { - if result.is_some() { - // Found two matches. - return None; - } - result = Some(data_current.offset(-(signature_offset as isize))); - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } else { - signature_offset += 1; - } - } else { - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } - - data_current = data_current.offset(1); - } - } - - result - } -} - -#[cfg(test)] -mod tests {} diff --git a/auxtools/src/sigscan/windows.rs b/auxtools/src/sigscan/windows.rs deleted file mode 100644 index 49ba09ed..00000000 --- a/auxtools/src/sigscan/windows.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{mem, ptr}; - -use winapi::{ - shared::minwindef, - um::{libloaderapi, processthreadsapi, psapi} -}; - -pub struct Scanner { - _module: minwindef::HMODULE, - data_begin: *mut u8, - data_end: *mut u8 -} - -impl Scanner { - pub fn for_module(name: &str) -> Option { - let mut module: minwindef::HMODULE = ptr::null_mut(); - let data_begin: *mut u8; - let data_end: *mut u8; - - // Construct a null-terminated UTF-16 string to pass to the Windows API - let name_winapi: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - unsafe { - if libloaderapi::GetModuleHandleExW(0, name_winapi.as_ptr(), &mut module) == 0 { - return None; - } - - let mut module_info_wrapper = mem::MaybeUninit::::zeroed(); - if psapi::GetModuleInformation( - processthreadsapi::GetCurrentProcess(), - module, - module_info_wrapper.as_mut_ptr(), - mem::size_of::() as u32 - ) == 0 - { - libloaderapi::FreeLibrary(module); - return None; - } - - let module_info = module_info_wrapper.assume_init(); - data_begin = module_info.lpBaseOfDll as *mut u8; - data_end = data_begin.offset(module_info.SizeOfImage as isize).offset(-1); - } - - Some(Scanner { - _module: module, - data_begin, - data_end - }) - } - - pub fn find(&self, signature: &[Option]) -> Option<*mut u8> { - let mut data_current = self.data_begin; - let data_end = self.data_end; - let mut signature_offset = 0; - let mut result: Option<*mut u8> = None; - - unsafe { - while data_current <= data_end { - if signature[signature_offset].is_none() || signature[signature_offset] == Some(*data_current) { - if signature.len() <= signature_offset + 1 { - if result.is_some() { - // Found two matches. - return None; - } - result = Some(data_current.offset(-(signature_offset as isize))); - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } else { - signature_offset += 1; - } - } else { - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } - - data_current = data_current.offset(1); - } - } - - result - } -} - -impl Drop for Scanner { - fn drop(&mut self) { - // TODO: WTf this started throwing?! - // unsafe { - // libloaderapi::FreeLibrary(self.module); - // } - } -} - -#[cfg(test)] -mod tests {} diff --git a/auxtools/src/string.rs b/auxtools/src/string.rs index 0d5c6f22..b9dac04d 100644 --- a/auxtools/src/string.rs +++ b/auxtools/src/string.rs @@ -25,8 +25,8 @@ impl StringRef { return None; } - // Here we're going from value -> raw -> new value because to get that juicy - // static lifetime + // Here we're going from value -> raw -> new value because to get that + // juicy static lifetime Some(StringRef { value: unsafe { Value::from_raw(value.raw) } }) @@ -86,8 +86,8 @@ impl fmt::Debug for StringRef { while let Some(&byte) = iter.next() { if byte == 0xFF { - // NOTE: Doesn't hold state for formatting, so some strings relying on are a - // little off + // NOTE: Doesn't hold state for formatting, so some strings + // relying on are a little off format.extend_from_slice(match iter.next() { None => break, Some(1) | Some(2) | Some(3) | Some(4) => b"[]", diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs new file mode 100644 index 00000000..9b787d11 --- /dev/null +++ b/auxtools/src/symbols.rs @@ -0,0 +1,128 @@ +//! Finds the BYOND functions and globals auxtools needs, through `byond-scan`. +//! +//! Every miss is an error that names the lookup. Nothing here hands back a null +//! pointer, because the C++ shim calls these functions without checking and +//! `debug.rs` reads the globals the same way. + +#[cfg(unix)] +mod linux; +#[cfg(windows)] +mod windows; + +use std::ffi::c_void; + +use byond_scan::{resolve_recipe, Module, Recipe}; +#[cfg(unix)] +use linux::RECIPES; +#[cfg(windows)] +use windows::RECIPES; + +use crate::raw_types::{funcs, lists, procs, variables::VariableNameIdTable}; + +/// The BYOND major version the patterns were checked against. The same build +/// number under another major is a different binary. +const SUPPORTED_VERSION: u32 = 516; + +fn module_and_build() -> Result<(Module, (u32, u32)), String> { + let module = Module::current().map_err(|err| format!("Couldn't locate BYOND core ({err})"))?; + let build = byond_scan::version().map_err(|err| format!("Couldn't read BYOND version ({err})"))?; + Ok((module, build)) +} + +fn recipe(name: &str) -> Result<&'static Recipe, String> { + RECIPES + .iter() + .find(|recipe| recipe.name == name) + .ok_or_else(|| format!("Couldn't find {name} (no such pattern)")) +} + +fn resolve(module: &Module, build: (u32, u32), name: &str) -> Result { + resolve_checked(module, build, recipe(name)?) +} + +fn resolve_checked(module: &Module, (version, build): (u32, u32), recipe: &Recipe) -> Result { + let name = recipe.name; + + // Older than anything checked is a hard stop. A newer build is still tried, + // and a pattern that has stopped matching fails with its own error. + if recipe.versions.applicability(version, build, SUPPORTED_VERSION).is_none() { + return Err(format!( + "Couldn't find {name} (BYOND {version}.{build} is outside the checked range {}..={})", + recipe.versions.min, recipe.versions.max + )); + } + + resolve_recipe(module, recipe, |_| None).map_err(|err| format!("Couldn't find {name} ({err})")) +} + +/// Resolves a recipe of your own with the same rules auxtools applies to its +/// own: builds older than the recipe's range are refused, newer ones are tried. +pub fn find_recipe(recipe: &Recipe) -> Result { + let (module, build) = module_and_build()?; + resolve_checked(&module, build, recipe) +} + +/// Looks up every function and global except `variable_names`, and hands them +/// to the C++ shim and `raw_types::funcs`. +pub(crate) fn resolve_full() -> Result<(), String> { + let (module, build) = module_and_build()?; + let address = |name| resolve(&module, build, name); + let function = |name| address(name).map(|address| address as *const c_void); + + unsafe { + funcs::CURRENT_EXECUTION_CONTEXT = address("current_execution_context")? as *mut *mut procs::ExecutionContext; + funcs::SUSPENDED_PROCS = address("suspended_procs")? as *mut procs::SuspendedProcs; + funcs::SUSPENDED_PROCS_BUFFER = address("suspended_procs_buffer")? as *mut procs::SuspendedProcsBuffer; + funcs::ALIST_TABLE = address("alist_table_ptr")? as *const *const *const lists::Alist; + funcs::ALIST_TABLE_COUNT = address("alist_table_count")? as *const u32; + funcs::call_proc_by_id_byond = function("call_proc_by_id")?; + funcs::call_datum_proc_by_name_byond = function("call_datum_proc_by_name")?; + funcs::get_proc_array_entry_byond = function("get_proc_array_entry")?; + funcs::get_string_id_byond = function("get_string_id")?; + funcs::get_variable_byond = function("get_variable")?; + funcs::set_variable_byond = function("set_variable")?; + funcs::get_string_table_entry_byond = function("get_string_table_entry")?; + funcs::inc_ref_count_byond = function("inc_ref_count")?; + funcs::dec_ref_count_byond = function("dec_ref_count")?; + funcs::get_assoc_element_byond = function("get_assoc_element")?; + funcs::set_assoc_element_byond = function("set_assoc_element")?; + funcs::create_list_byond = function("create_list")?; + funcs::append_to_list_byond = function("append_to_list")?; + funcs::remove_from_list_byond = function("remove_from_list")?; + #[cfg(unix)] + { + funcs::remove_from_list_in_registers = build.1 < linux::REMOVE_FROM_LIST_STACK_BUILD; + } + funcs::get_length_byond = function("get_length")?; + funcs::value_is_list_byond = function("value_is_list")?; + funcs::get_misc_by_id_byond = function("get_misc_by_id")?; + funcs::to_string_byond = function("to_string")?; + funcs::runtime_byond = function("runtime")?; + } + + Ok(()) +} + +/// Looks up `variable_names`. BYOND reallocates it for every world, so this +/// runs on every partial init and not only the first. +pub(crate) fn resolve_partial() -> Result<(), String> { + let (module, build) = module_and_build()?; + // the recipe finds the name array, which is not where the table starts on + // Linux + let address = resolve(&module, build, "variable_names")? - std::mem::offset_of!(VariableNameIdTable, entries); + unsafe { + funcs::VARIABLE_NAMES = address as *const VariableNameIdTable; + } + Ok(()) +} + +/// The names that fail to resolve on the running build, for +/// `auxtools_check_signatures`. +pub(crate) fn unresolved() -> Result, String> { + let (module, build) = module_and_build()?; + Ok(RECIPES + .iter() + .map(|recipe| recipe.name) + .filter(|name| resolve(&module, build, name).is_err()) + .collect()) +} diff --git a/auxtools/src/symbols/linux.rs b/auxtools/src/symbols/linux.rs new file mode 100644 index 00000000..b224cc5e --- /dev/null +++ b/auxtools/src/symbols/linux.rs @@ -0,0 +1,314 @@ +//! Where auxtools finds its BYOND functions and globals in `libbyond.so`. +//! +//! Copied from `byond_catalog` in the byond-re repo, where `verify_recipes` +//! checks each pattern against every local build. Keep the two in step. + +use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, VersionRange}; + +// Checked on every build in this range. Older builds are refused by the +// resolver, newer ones are tried and can still fail. +// +// The floor is 1664 and not Windows' 1659 because of proc hooks. On 1659 GCC +// inlined `call_proc_by_id` into the interpreter, so a DM proc call never +// enters the function the hook patches and no `#[hook]` ever fires. 1660 to +// 1663 were never checked. +const SUPPORTED: VersionRange = VersionRange { min: 1664, max: 1688 }; + +// The first build where `remove_from_list` takes its arguments on the stack. +// Every build before it wants the list in `eax:edx`. Read off the prologue of +// every build from 1669 to 1675. +pub(crate) const REMOVE_FROM_LIST_STACK_BUILD: u32 = 1674; + +// One loader mask finds the variable-name table. Operands that don't use a +// register are count=0, global_values=1 and variable_names=2. +const GLOBAL_VARS_SIG: &str = "89 1D ?? ?? ?? ?? 57 E8 ?? ?? ?? ?? 83 C4 10 85 FF 74 08 85 C0 0F 84 ?? ?? ?? ?? 83 EC 0C C1 E3 02 A3 ?? ?? ?? ?? 53 \ + E8 ?? ?? ?? ?? 83 C4 10 85 DB 74 08 85 C0 0F 84 ?? ?? ?? ?? A3 ?? ?? ?? ??"; + +// The prefix tells this enqueue apart from the dequeue clones. Operands are +// back=0, front=1, buffer=2. The two index globals sit the other way round from +// Windows, so back is 4 bytes below front. +const SUSPENDED_SIG: &str = "55 57 89 C7 56 53 83 EC ?? 8B 0D ?? ?? ?? ?? 80 48 04 04 8B A8 8C 00 00 00 A1 ?? ?? ?? ?? 89 4C 24 ?? 8B 35 ?? ?? ?? ??"; + +// `runtime` opens with a load of the current execution context, so the same +// mask finds both. +const RUNTIME_SIG: &str = + "55 89 E5 56 53 83 EC ?? 8B 15 ?? ?? ?? ?? 85 D2 0F 84 ?? ?? ?? ?? 0F B6 42 6F 3C 01 76 ?? 31 C9 84 C0 C7 42 10 ?? ?? ?? ?? 0F 94 C3"; + +const PROCDEF_ACCESSOR_SIG: &str = "8B 44 24 04 39 05 ?? ?? ?? ?? 76 ?? 6B C0 ?? 03 05 ?? ?? ?? ?? C3"; +const STRING_ACCESSOR_SIG: &str = "56 53 83 EC 14 8B 44 24 20 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 82"; + +// The 0x0F/0x55 tag dispatch identifies the accessor that reads the alist +// table. Operands are count=0 and pointer=1. +const ALIST_SIG: &str = + "8B 54 24 04 8B 4C 24 08 80 FA 0F 74 ?? 31 C0 80 FA 55 75 ?? 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 74 ?? 8B 00 C3"; + +pub(crate) const RECIPES: &[Recipe] = &[ + Recipe { + name: "get_proc_array_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, PROCDEF_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_table_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, STRING_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_id", + versions: SUPPORTED, + // frameless mkstr, pinned by the comparison against 0x01 + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 57 56 89 CE 53 89 D3 83 EC ?? 8B 54 24 ?? 88 54 24 ?? 85 C0 0F 84 ?? ?? ?? ?? 89 44 24 ?? 84 DB 0F 88 ?? ?? ?? ?? 88 5C 24 ?? 80 FB \ + 01 0F 84 ?? ?? ?? ?? 89 F0 84 C0" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_proc_by_id", + versions: SUPPORTED, + // the frame that builds a call record and drops into the interpreter. Every + // `mov reg,[esp+disp]` argument load has its displacement wildcarded because + // they all move with the frame. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 31 C0 57 56 53 81 EC ?? ?? ?? ?? F3 0F 7E 8C 24 ?? ?? ?? ?? 8B 8C 24 ?? ?? ?? ?? 8A 84 24 ?? ?? ?? ?? 8B 9C 24 ?? ?? ?? ?? F3 0F 7E \ + 84 24" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_datum_proc_by_name", + versions: SUPPORTED, + // positive `ebp` displacements are incoming arguments and stay pinned. The + // negative ones are locals and move with the frame. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 31 C9 89 E5 57 56 8D 45 ?? 53 83 EC ?? F3 0F 7E 45 0C 8B 5D 14 C6 45 ?? 00 8B 55 18 8B 75 08 6A 00 66 0F D6 45 ?? 0F B6 FB 66 0F 7E \ + 45 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_variable", + versions: SUPPORTED, + // `3C 58` is `cmp al, 58h`, the tag bound of the 89-case switch, and it is + // what makes this unique + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 75 08 8B 5D 14 66 0F 6F C1 66 0F 7E CA 66 0F 73 D0 20 0F B6 C2 66 0F 7E 45 ?? 3C 58 77 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_variable", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 55 08 8B 7D 10 66 0F 6F C2 66 0F 7E D2 66 0F 73 D0 20 89 D3 66 0F 7E C6 F3 0F 7E 45 14 89 75 ?? 66 \ + 0F 6F C8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "inc_ref_count", + versions: SUPPORTED, + // `80 F9 4A` is `cmp cl, 4Ah`, the tag table bound. The `66 0F 7E C0` before + // it is the only thing telling this apart from dec_ref_count in the first 48 + // bytes. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 56 53 83 EC ?? F3 0F 7E 4D 08 66 0F 6F C1 66 0F 7E C9 66 0F 73 D0 20 66 0F 7E C0 80 F9 4A 77 ?? 0F B6 C9 FF 24 8D ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "dec_ref_count", + versions: SUPPORTED, + // the fifth call out of the exported ByondValue_DecRef. A shifted call chain + // fails silently, so byond-re checks this index with a direct mask as well. + anchor: Anchor::Export(c"ByondValue_DecRef"), + hops: &[4], + extract: Extract::Entry + }, + Recipe { + name: "get_assoc_element", + versions: SUPPORTED, + // splits from set_assoc_element at +0x0E: `8B 7D` here against the setter's + // `66 0F 6F`. Everything before that is identical or wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 7D 08 66 0F 6F C1 66 0F 7E C8 66 0F 7E 4D ?? 66 0F 73 D0 20 66 0F 7E 45 ?? 3C 3C 0F 84 ?? \ + ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_assoc_element", + versions: SUPPORTED, + // the prologue changes after 1659, so follow this caller. It is unique across + // the range, and its alignment padding is wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 20 E9 ?? ?? ?? ?? ?? ?? ?? ?? 31 C9 EB ?? D9 43 04 BA 01 00 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "create_list", + versions: SUPPORTED, + // `81 FB FF FF 00 00` is `cmp ebx, 0FFFFh`, the failure sentinel. It is what + // tells this apart from the `new` dispatcher, which opens the same way. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 57 56 53 83 EC ?? A1 ?? ?? ?? ?? 8B 7C 24 ?? 85 C0 0F 84 ?? ?? ?? ?? 8B 15 ?? ?? ?? ?? 83 E8 01 A3 ?? ?? ?? ?? 8B 1C 82 81 FB FF FF \ + 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "append_to_list", + versions: SUPPORTED, + // the Value arrives in registers, so the head is a run of movd/punpckldq + // instead of stack loads. It opens with `56` where remove_from_list opens + // with `55`. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "56 66 0F 6E CA 66 0F 6E C0 53 66 0F 62 C1 66 0F 7E C6 66 0F 6F D0 66 0F 73 D0 20 89 F1 83 EC ?? 66 0F 7E C0 F3 0F 7E 4C 24 ?? 66 0F 6F \ + C1" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "remove_from_list", + versions: SUPPORTED, + // the prologue mask only covers up to 1669 and the head changes at 1675, so + // follow this caller, which is unique on every build. Its `ebp` + // displacements are wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 85 ?? ?? ?? ?? 01 8B 8D ?? ?? ?? ?? 83 C4 10 83 85 ?? ?? ?? ?? 08 39" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_length", + versions: SUPPORTED, + // the tag-switch gate of the real length helper. An older mask resolved + // value_is_list instead. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 5D 08 66 0F 7E CA 83 EA 0C 80 FA 4B 77 ?? 66 0F 6F C1 0F B6 D2 66 0F 73 D0 20 66 0F 7E C0 \ + FF 24 95 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "value_is_list", + versions: SUPPORTED, + // this is the function the `islist()` opcode calls. The exported + // `ByondValue_IsList` is not used because on Windows 1659 it says no to a + // `filters` list, and both platforms should ask the same function. + // `80 FA 55` is `cmp dl, 55h`, the highest tag it accepts + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 56 53 83 EC ?? F3 0F 7E 4D 08 8B 75 10 66 0F 7E CA 80 FA 55 0F 87 ?? ?? ?? ?? 80 FA 3B 76 ?? 8D 4A C4 B8 01 00 00 00 D3 E0 89 \ + C1" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_misc_by_id", + versions: SUPPORTED, + // the plain accessor body matches 17 times, so follow a unique caller + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? 83 C4 10 31 DB 85 C0 74 03 0F B7 18 8B 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "to_string", + versions: SUPPORTED, + // the 89-case interning switch. Stops before unstable alignment padding. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? 0F B6 45 08 3C 45 77 ?? 3C 27 77 ?? 8D 50 FF 80 FA 26 77 ?? FF 24 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "runtime", + versions: SUPPORTED, + // throws through __cxa_throw here, where Windows uses _CxxThrowException + anchor: Anchor::Signature(SignatureTreatment::NoOffset, RUNTIME_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "current_execution_context", + versions: SUPPORTED, + // `runtime` opens with a load of this global, and nothing touches memory + // before that, so it is operand 0 + anchor: Anchor::Signature(SignatureTreatment::NoOffset, RUNTIME_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + // BYOND reallocates this per world load, so it is looked up again on every + // partial init. + Recipe { + name: "variable_names", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, GLOBAL_VARS_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + // the start of the sleeping-proc queue: back, then front + Recipe { + name: "suspended_procs", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "suspended_procs_buffer", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + Recipe { + name: "alist_table_count", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "alist_table_ptr", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(1)) + } +]; diff --git a/auxtools/src/symbols/windows.rs b/auxtools/src/symbols/windows.rs new file mode 100644 index 00000000..dcc74e04 --- /dev/null +++ b/auxtools/src/symbols/windows.rs @@ -0,0 +1,284 @@ +//! Where auxtools finds its BYOND functions and globals in `byondcore.dll`. +//! +//! Copied from `byond_catalog` in the byond-re repo, where `verify_recipes` +//! checks each pattern against every local build. Keep the two in step. + +use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, VersionRange}; + +// Checked on every build in this range. Older builds are refused by the +// resolver, newer ones are tried and can still fail. +const SUPPORTED: VersionRange = VersionRange { min: 1659, max: 1688 }; + +// One loader mask finds the variable-name table. Operands that don't use a +// register are count=0, global_values=1, the sizing lea=3 and variable_names=4. +// The lea is why variable_names is not index 3. +const GLOBAL_VARS_SIG: &str = "A3 ?? ?? ?? ?? 56 FF D3 83 C4 04 85 F6 74 08 85 C0 0F 84 4B 01 00 00 A3 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8D 34 85 00 00 00 \ + 00 56 FF D3 83 C4 04 85 F6 74 08 85 C0 0F 84 2D 01 00 00 33 DB A3 ?? ?? ?? ?? 39 1D"; + +// The prologue tells this queue access apart from its dequeue clone. Operands +// are front=0, back=1, buffer=2. +const SUSPENDED_SIG: &str = + "55 8B EC 56 8B 75 08 56 80 4E 04 04 E8 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 83 C4 04 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88"; + +// The procdef accessor has a 44-byte stride, which sets it apart from the other +// table accessors. +const PROCDEF_ACCESSOR_SIG: &str = "55 8B EC 8B 45 08 3B 05 ?? ?? ?? ?? 72 04 33 C0 5D C3 6B C0 2C 03 05"; +// The branch distance varies, so it is wildcarded and the mask keeps a longer +// tail. +const STRING_ACCESSOR_SIG: &str = "55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 0F 85"; + +const ALIST_SIG: &str = "3B 35 ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? C7 04 B0 00 00 00 00 46 3B 35 ?? ?? ?? ?? 72 EB"; + +pub(crate) const RECIPES: &[Recipe] = &[ + Recipe { + name: "get_proc_array_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, PROCDEF_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_table_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, STRING_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_id", + versions: SUPPORTED, + // mkstr, where string interning goes in + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 45 ?? 83 EC ?? 53 56 8B 35 ?? ?? ?? ?? 57 85 C0 75 ?? 68 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_proc_by_id", + versions: SUPPORTED, + // the frame size is wildcarded because the compiler picks it + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 81 EC ?? ?? ?? ?? A1 ?? ?? ?? ?? 33 C5 89 45 ?? 8B 55 ?? 8B 45 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_datum_proc_by_name", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 1C 8D 45 \ + F3 8B 7D 18 8B 5D 10 6A 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_variable", + versions: SUPPORTED, + // the 0x0C frame size has to stay pinned: without it the mask matches one of + // several common SEH prologues. A changed frame should fail as NotFound. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 8B 75 \ + ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_variable", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "inc_ref_count", + versions: SUPPORTED, + // no prologue worth masking, so anchor on a call site and follow it + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? FF ?? ?? FF ?? ?? E8 ?? ?? ?? ?? 8D ?? ?? 56 E8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "dec_ref_count", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_assoc_element", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 51 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_assoc_element", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 80 FB \ + 0F" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "create_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 0D ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 0D ?? ?? ?? ?? 8B 34 88 81 FE FF FF 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "append_to_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 4D ?? 0F B6 C1 48 56 57 83 F8 54 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "remove_from_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 83 EC 08 53 8B 5D ?? 0F B6 C3 48 56 57 83 F8 54 0F 87 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_length", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 4D ?? 83 EC ?? 0F B6 C1 48 53 56 57 83 F8 ?? 0F 87 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "value_is_list", + versions: SUPPORTED, + // this is the function the `islist()` opcode calls. The exported + // `ByondValue_IsList` is not used because on 1659 it says no to a `filters` + // list. The prologue is a common one, and the tag-range check at the end + // (`83 C0 F1 83 F8 46`) is what makes it unique + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 89 65 ?? 8B 45 \ + 08 0F B6 C0 83 C0 F1 83 F8 46" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_misc_by_id", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 04 85 C0 74 08 0F B7 38 8B 70 08 EB 04 33 FF 33 F6 0F B7 C7 50 89 45 F8 E8 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "to_string", + versions: SUPPORTED, + // the prologue changes at 1686 and any shared prefix is ambiguous, so follow + // this caller instead. It is unique across the whole range. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 08 8B F0 EB ?? 80 FB 2A 75 ?? 66 83 7D F8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "runtime", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 04 8B 85 ?? ?? ?? ?? 0F B6 C0 51 66 0F 6E C0 0F 5B C0" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "current_execution_context", + versions: SUPPORTED, + // the `A1` load from an absolute address has no base register, which makes + // it operand 0 + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "A1 ?? ?? ?? ?? 56 53 6A 00 8B 00 57 6A ?? 89 4D ?? FF 70 ?? 8B 4D ?? FF" + ), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + // BYOND reallocates this per world load, so it is looked up again on every + // partial init. + Recipe { + name: "variable_names", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, GLOBAL_VARS_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(4)) + }, + // the start of the sleeping-proc queue: front, then back, then a capacity + Recipe { + name: "suspended_procs", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "suspended_procs_buffer", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + // the alist accessor's base-less operands are count=0, table=1, and the + // repeated count=2 + Recipe { + name: "alist_table_count", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "alist_table_ptr", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(1)) + } +]; diff --git a/auxtools/src/value.rs b/auxtools/src/value.rs index 50e5c020..59f4efe5 100644 --- a/auxtools/src/value.rs +++ b/auxtools/src/value.rs @@ -316,7 +316,7 @@ impl Value { } /// Creates a Value that references a byond string. - /// Will panic if the given string contains null bytes + /// Returns an error if the given string contains null bytes /// /// # Examples: /// ```ignore diff --git a/auxtools/src/value_from.rs b/auxtools/src/value_from.rs index edb89de5..c8dd898c 100644 --- a/auxtools/src/value_from.rs +++ b/auxtools/src/value_from.rs @@ -72,7 +72,8 @@ impl + Clone, B: Into + Clone> TryFrom<&HashMap> for for (k, v) in hashmap { // This can fail for basically any reason that BYOND decides, - // because in the end this just ends up calling into BYOND with the Value's. + // because in the end this just ends up calling into BYOND with the + // Value's. res.set(k.clone(), v.clone())?; } diff --git a/auxtools/src/version.rs b/auxtools/src/version.rs index 70ceff66..8a845c36 100644 --- a/auxtools/src/version.rs +++ b/auxtools/src/version.rs @@ -1,82 +1,12 @@ -use super::*; -use std::{ffi::CString, os::raw::c_char}; - pub static mut BYOND_VERSION_MAJOR: u32 = 0; pub static mut BYOND_VERSION_MINOR: u32 = 0; -#[cfg(windows)] -static GET_BYOND_VERSION_SYMBOL: &[u8] = b"?GetByondVersion@ByondLib@@QAEJXZ\0"; - -#[cfg(windows)] -static GET_BYOND_BUILD_SYMBOL: &[u8] = b"?GetByondBuild@ByondLib@@QAEJXZ\0"; - -#[cfg(unix)] -static GET_BYOND_VERSION_SYMBOL: &[u8] = b"_ZN8ByondLib15GetByondVersionEv\0"; - -#[cfg(unix)] -static GET_BYOND_BUILD_SYMBOL: &[u8] = b"_ZN8ByondLib13GetByondBuildEv\0"; - pub fn init() -> Result<(), String> { - let get_byond_version: extern "C" fn() -> u32; - let get_byond_build: extern "C" fn() -> u32; - - #[cfg(windows)] - { - use winapi::um::libloaderapi; - - unsafe { - let mut module = std::ptr::null_mut(); - let core_str = CString::new(BYONDCORE).unwrap(); - if libloaderapi::GetModuleHandleExA(0, core_str.as_ptr(), &mut module) == 0 { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = libloaderapi::GetProcAddress(module, GET_BYOND_VERSION_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_version in BYONDCORE".into()); - } - - get_byond_version = std::mem::transmute(symbol); - - let symbol = libloaderapi::GetProcAddress(module, GET_BYOND_BUILD_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_build in BYONDCORE".into()); - } - - get_byond_build = std::mem::transmute(symbol); - } - } - - #[cfg(unix)] - { - use libc::{dlopen, dlsym, RTLD_LAZY}; - - unsafe { - let byond_core_str = CString::new(BYONDCORE).unwrap(); - let module = dlopen(byond_core_str.as_ptr(), RTLD_LAZY); - if module.is_null() { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = dlsym(module, GET_BYOND_VERSION_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_version in BYONDCORE".into()); - } - - get_byond_version = std::mem::transmute(symbol); - - let symbol = dlsym(module, GET_BYOND_BUILD_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_build in BYONDCORE".into()); - } - - get_byond_build = std::mem::transmute(symbol); - } - } + let (major, minor) = byond_scan::version().map_err(|err| format!("Couldn't read the BYOND version ({err})"))?; unsafe { - BYOND_VERSION_MAJOR = get_byond_version(); - BYOND_VERSION_MINOR = get_byond_build(); + BYOND_VERSION_MAJOR = major; + BYOND_VERSION_MINOR = minor; } Ok(()) diff --git a/debug_server/Cargo.toml b/debug_server/Cargo.toml index fff1b54f..597cc529 100644 --- a/debug_server/Cargo.toml +++ b/debug_server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "debug_server" -version = "2.3.8" +version = "2.4.0" publish = false authors.workspace = true edition.workspace = true @@ -23,10 +23,7 @@ region = "3" retour = { workspace = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "libloaderapi", "errhandlingapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" +winapi = { version = "0.3", features = ["libloaderapi", "errhandlingapi", "ioapiset", "processthreadsapi"] } [lints] workspace = true diff --git a/debug_server/src/assemble_env.rs b/debug_server/src/assemble_env.rs index ec6ddefe..57b9fcf3 100644 --- a/debug_server/src/assemble_env.rs +++ b/debug_server/src/assemble_env.rs @@ -7,7 +7,8 @@ impl dmasm::assembler::AssembleEnv for AssembleEnv { let string = StringRef::from_raw(string).ok()?; let id = string.get_id(); - // We leak here because the assembled code now holds a reference to this string + // We leak here because the assembled code now holds a reference to this + // string std::mem::forget(string); Some(id.0) diff --git a/debug_server/src/ckey_override.rs b/debug_server/src/ckey_override.rs index a21fcf3b..85b0a409 100644 --- a/debug_server/src/ckey_override.rs +++ b/debug_server/src/ckey_override.rs @@ -1,23 +1,35 @@ use std::{ffi::CString, os::raw::c_char}; +#[cfg(windows)] +use auxtools::byond_scan::{Anchor, Extract, Recipe, SignatureTreatment, VersionRange}; use auxtools::*; use region::Protection; static mut STRING_PTR: *mut *const c_char = std::ptr::null_mut(); +// The `push` of the "Guest-%u" format string. We want the address of its 4-byte +// immediate, one byte past the opcode, so we can swap the string pointer out. +// Copied from `byond_catalog` in the byond-re repo, which checks it against +// every local build. +#[cfg(windows)] +const GUEST_NAME_FORMAT: Recipe = Recipe { + name: "guest_name_format", + versions: VersionRange { min: 1659, max: 1688 }, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "68 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 0C 8D 8D ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Offset(1) +}; + #[init(full)] fn ckey_override_init() -> Result<(), String> { + // This feature soft-fails #[cfg(windows)] - { - let byondcore = sigscan::Scanner::for_module(BYONDCORE).unwrap(); - - // This feature soft-fails - if let Some(ptr) = byondcore.find(signature!( - "68 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 0C 8D 8D ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 85 ?? ?? ?? ??" - )) { - unsafe { - STRING_PTR = ptr.add(1) as *mut *const c_char; - } + if let Ok(address) = find_recipe(&GUEST_NAME_FORMAT) { + unsafe { + STRING_PTR = address as *mut *const c_char; } } diff --git a/debug_server/src/instruction_hooking.rs b/debug_server/src/instruction_hooking.rs index e1834ed0..d57fd792 100644 --- a/debug_server/src/instruction_hooking.rs +++ b/debug_server/src/instruction_hooking.rs @@ -34,7 +34,8 @@ impl ProcInstanceRef { fn new(ptr: *mut raw_types::procs::ProcInstance) -> Self { unsafe { static mut PTR_REF_ID: u16 = 0x8000; - PTR_REF_ID += 1; + // Every step takes a new id, so a long session does run out of them + PTR_REF_ID = if PTR_REF_ID == u16::MAX { 0x8001 } else { PTR_REF_ID + 1 }; (*ptr).mega_hack = PTR_REF_ID; Self(PTR_REF_ID) } @@ -185,7 +186,9 @@ fn proc_instance_is_suspended(proc_ref: ProcInstanceRef) -> bool { #[runtime_handler] fn handle_runtime(error: &str) { - unsafe { + // BYOND calls this through extern "C" code, and a panic that gets back out + // there aborts the whole game + let result = std::panic::catch_unwind(|| unsafe { let ctx = *raw_types::funcs::CURRENT_EXECUTION_CONTEXT; // If this is eval code, don't catch the breakpoint @@ -198,13 +201,19 @@ fn handle_runtime(error: &str) { } CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Runtime(error.to_string())); + }); + + if result.is_err() { + if let Some(server) = unsafe { &mut *DEBUG_SERVER.get() } { + server.recover_from_panic(); + } } } impl InstructionHook for Server { fn handle_instruction(&mut self, ctx: *mut raw_types::procs::ExecutionContext) { - // Always handle the deferred instruction replacement first - everything else - // will depend on it + // Always handle the deferred instruction replacement first - everything + // else will depend on it unsafe { let deferred = DEFERRED_INSTRUCTION_REPLACE.get(); if let Some((src, dst)) = &*deferred { @@ -213,8 +222,14 @@ impl InstructionHook for Server { } } + // An expression the debugger is evaluating runs through here too. + // Pausing in the middle of it would start a second pause inside the + // one that's waiting on the answer, so all it gets is its breakpoints + // stepped over. let server = self; - if server.process() { + let in_eval = server.is_in_eval(); + + if !in_eval && server.process() { unsafe { CURRENT_ACTION = DebuggerAction::Pause; } @@ -224,12 +239,14 @@ impl InstructionHook for Server { let opcode = unsafe { *opcode_ptr }; let is_dbgline = opcode == OPCODE_DBGLINE; - // This lets us ignore any actual breakpoints we hit if we've already paused for - // another reason + // This lets us ignore any actual breakpoints we hit if we've already + // paused for another reason let mut did_breakpoint = false; unsafe { - match CURRENT_ACTION { + let action = if in_eval { DebuggerAction::None } else { CURRENT_ACTION }; + + match action { DebuggerAction::None => {} DebuggerAction::Pause => { @@ -251,8 +268,9 @@ impl InstructionHook for Server { if is_dbgline && target.is(*(*ctx).proc_instance()) { CURRENT_ACTION = DebuggerAction::BreakOnNext; } else { - // If the context isn't in any stacks, it has just returned. Break! - // TODO: Don't break if the context's stack is gone (returned to C) + // If the context isn't in any stacks, it has just + // returned. Break! TODO: Don't + // break if the context's stack is gone (returned to C) if !proc_instance_is_in_stack(ctx, target) && !proc_instance_is_suspended(target) { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Step); @@ -275,8 +293,10 @@ impl InstructionHook for Server { let in_stack = proc_instance_is_in_stack(ctx, parent); let is_suspended = proc_instance_is_suspended(parent); - // If the context isn't in any stacks, it has just returned. Break! - // TODO: Don't break if the context's stack is gone (returned to C) + // If the context isn't in any stacks, it has just + // returned. Break! + // TODO: Don't break if the context's stack is gone + // (returned to C) if !in_stack && !is_suspended { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Step); @@ -311,20 +331,25 @@ impl InstructionHook for Server { if opcode == OPCODE_DEBUG_BREAK { // We don't want to break twice when stepping on to a breakpoint - if !did_breakpoint { + if !did_breakpoint && !in_eval { unsafe { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Breakpoint); } } - // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already been - // removed + // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has + // already been removed let map = ORIGINAL_BYTECODE.lock().unwrap(); if let Some(original) = map.get(&PtrKey::new(opcode_ptr)) { unsafe { + // The breakpoint we stepped over last is normally back in + // place by now. It isn't if an eval ended on that very + // instruction, so put it back before we lose track of it. let deferred_replace = DEFERRED_INSTRUCTION_REPLACE.get(); - assert_eq!(*deferred_replace, None); + if let Some((src, dst)) = &*deferred_replace { + std::ptr::copy_nonoverlapping(src.as_ptr(), *dst, src.len()); + } *deferred_replace = Some((std::slice::from_raw_parts(opcode_ptr, original.len()).to_vec(), opcode_ptr)); std::ptr::copy_nonoverlapping(original.as_ptr(), opcode_ptr, original.len()); } @@ -410,8 +435,8 @@ pub fn unhook_instruction(proc: &Proc, offset: u32) -> Result<(), InstructionUnh bytecode.as_mut_ptr().add(offset as usize) }; - // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already been - // removed + // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already + // been removed let mut map = ORIGINAL_BYTECODE.lock().unwrap(); if let Some(original) = map.get(&PtrKey::new(opcode_ptr)) { unsafe { @@ -430,21 +455,36 @@ pub fn unhook_instruction(proc: &Proc, offset: u32) -> Result<(), InstructionUnh Ok(()) } -pub fn get_hooked_offsets(proc: &Proc) -> Vec { - let bytecode = unsafe { proc.bytecode() }; +/// Takes every breakpoint back out of the bytecode and forgets any step in +/// progress. +pub fn unhook_all() { + unsafe { + CURRENT_ACTION = DebuggerAction::None; + *DEFERRED_INSTRUCTION_REPLACE.get() = None; + } - let mut env = disassemble_env::DisassembleEnv; - let (nodes, _error) = dmasm::disassembler::disassemble(bytecode, &mut env); + for (opcode_ptr, original) in ORIGINAL_BYTECODE.lock().unwrap().drain() { + unsafe { + std::ptr::copy_nonoverlapping(original.as_ptr(), opcode_ptr.0 as *mut u32, original.len()); + } + } +} - let mut offsets = vec![]; +/// The proc's bytecode without any of our breakpoints patched into it. +pub fn original_bytecode(proc: &Proc) -> Vec { + let (start, count) = unsafe { proc.bytecode_mut_ptr() }; + let mut bytecode = unsafe { std::slice::from_raw_parts(start, count as usize) }.to_vec(); - for node in nodes { - if let dmasm::Node::Instruction(ins, debug) = node { - if ins == dmasm::Instruction::AuxtoolsDebugBreak { - offsets.push(debug.offset); - } + for (opcode_ptr, original) in ORIGINAL_BYTECODE.lock().unwrap().iter() { + // The map holds every proc's breakpoints, and most aren't in this one + let Some(offset) = opcode_ptr.0.checked_sub(start as usize).map(|bytes| bytes / std::mem::size_of::()) else { + continue; + }; + + if let Some(hooked) = bytecode.get_mut(offset..offset + original.len()) { + hooked.copy_from_slice(original); } } - offsets + bytecode } diff --git a/debug_server/src/lib.rs b/debug_server/src/lib.rs index 4047d11a..bf1522da 100644 --- a/debug_server/src/lib.rs +++ b/debug_server/src/lib.rs @@ -16,7 +16,8 @@ mod mem_profiler_stub; use std::{ cell::UnsafeCell, - net::{IpAddr, Ipv4Addr, SocketAddr} + net::{IpAddr, Ipv4Addr, SocketAddr}, + panic::{catch_unwind, AssertUnwindSafe} }; pub(crate) use ::instruction_hooking::disassemble_env::DisassembleEnv; @@ -56,8 +57,15 @@ struct DebugServerInstructionHook<'a> { impl InstructionHook for DebugServerInstructionHook<'static> { fn handle_instruction(&mut self, ctx: *mut raw_types::procs::ExecutionContext) { - if let Some(debug_server) = self.debug_server.get_mut() { - debug_server.handle_instruction(ctx); + let Some(debug_server) = self.debug_server.get_mut() else { + return; + }; + + // BYOND calls this through extern "C" code, and a panic that gets back + // out there aborts the whole game. Losing the debugger is the better + // deal. + if catch_unwind(AssertUnwindSafe(|| debug_server.handle_instruction(ctx))).is_err() { + debug_server.recover_from_panic(); } } } @@ -69,6 +77,12 @@ fn enable_debugging(mode: Value, port: Value) { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + // A second server would mean a second instruction hook, and both hooks + // would run the one server on every instruction + if mode != "NONE" && unsafe { (*DEBUG_SERVER.get()).is_some() } { + return Err(runtime!("debugging is already enabled")); + } + let server = match mode.as_str() { "NONE" => { return Ok(Value::NULL); diff --git a/debug_server/src/mem_profiler.rs b/debug_server/src/mem_profiler.rs index e4490377..f2e61521 100644 --- a/debug_server/src/mem_profiler.rs +++ b/debug_server/src/mem_profiler.rs @@ -211,7 +211,7 @@ impl State { totals[proc_idx].1 += size as u64; } - totals.sort_by(|x, y| x.1.cmp(&y.1)); + totals.sort_by_key(|x| x.1); for (proc, total) in totals { if let Some(proc) = Proc::from_id(proc) { diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 41883e34..795aec86 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -4,7 +4,10 @@ use std::{ error::Error, io::{Read, Write}, net::{SocketAddr, TcpListener, TcpStream}, - sync::mpsc, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc, Arc + }, thread, thread::JoinHandle }; @@ -17,7 +20,7 @@ use clap::{Arg, Command}; use instruction_hooking::disassemble_env; use super::{ - instruction_hooking::{get_hooked_offsets, hook_instruction, unhook_instruction}, + instruction_hooking::{hook_instruction, original_bytecode, unhook_all, unhook_instruction}, server_types::* }; use crate::mem_profiler; @@ -70,28 +73,34 @@ impl State { // Server = main-thread code // ServerThread = networking-thread code // -// We've got a couple of channels going on between Server/ServerThread -// connection: a TcpStream sent from the ServerThread for the Server to send -// responses on requests: requests from the debug-client for the Server to -// handle +// The ServerThread tells the Server everything through one channel of Events, +// so they arrive in the order they happened. // -// Limitations: only ever accepts one connection +// Limitations: one debug-client at a time. Another can connect once it leaves. // -enum ServerStream { - // The server is waiting for a Stream to be sent on the connection channel - Waiting(mpsc::Receiver), - +enum Event { + // A debug-client arrived. The TcpStream is for the Server to send responses on Connected(TcpStream), - // The server has finished being used + // A request from the debug-client for the Server to handle + Request(Request), + + // The debug-client left, or its connection broke Disconnected } +// Nothing a debug-client sends is anywhere near this big. Without a limit, any +// stray bytes on the port get read as a length and we try to allocate it. +const MAX_REQUEST_LEN: u32 = 1024 * 1024; + pub struct Server { - requests: mpsc::Receiver, - stream: ServerStream, - _thread: JoinHandle<()>, + events: mpsc::Receiver, + stream: Option, + thread: Option>, + // Where our listener is bound, when we have one + listen_addr: Option, + stop: Arc, should_catch_runtimes: bool, state: Option, in_eval: bool, @@ -101,13 +110,13 @@ pub struct Server { } struct ServerThread { - requests: mpsc::Sender + events: mpsc::Sender } impl Server { pub fn setup_app() -> Command<'static> { Command::new("Auxtools Debug Server") - .version("2.2.2") + .version(env!("CARGO_PKG_VERSION")) .subcommand_required(true) .no_binary_name(true) .color(clap::ColorChoice::Never) @@ -149,19 +158,21 @@ impl Server { pub fn connect(addr: &SocketAddr) -> std::io::Result { let stream = TcpStream::connect_timeout(addr, std::time::Duration::from_secs(5))?; - let (requests_sender, requests_receiver) = mpsc::channel(); + let (events_sender, events_receiver) = mpsc::channel(); - let server_thread = ServerThread { requests: requests_sender }; + let server_thread = ServerThread { events: events_sender }; - let cloned_stream = stream.try_clone().unwrap(); + let cloned_stream = stream.try_clone()?; let thread = thread::spawn(move || { server_thread.run(cloned_stream); }); let mut server = Server { - requests: requests_receiver, - stream: ServerStream::Connected(stream), - _thread: thread, + events: events_receiver, + stream: Some(stream), + thread: Some(thread), + listen_addr: None, + stop: Arc::new(AtomicBool::new(false)), should_catch_runtimes: true, state: None, in_eval: false, @@ -175,15 +186,21 @@ impl Server { } pub fn listen(addr: &SocketAddr) -> std::io::Result { - let (connection_sender, connection_receiver) = mpsc::channel(); - let (requests_sender, requests_receiver) = mpsc::channel(); + let (events_sender, events_receiver) = mpsc::channel(); + let stop = Arc::new(AtomicBool::new(false)); - let thread = ServerThread { requests: requests_sender }.spawn_listener(TcpListener::bind(addr)?, connection_sender); + let listener = TcpListener::bind(addr)?; + // Asking for port 0 gets whichever port the OS picks, and Server::drop + // has to connect to that one + let listen_addr = listener.local_addr()?; + let thread = ServerThread { events: events_sender }.spawn_listener(listener, stop.clone()); Ok(Server { - requests: requests_receiver, - stream: ServerStream::Waiting(connection_receiver), - _thread: thread, + events: events_receiver, + stream: None, + thread: Some(thread), + listen_addr: Some(listen_addr), + stop, should_catch_runtimes: true, state: None, in_eval: false, @@ -269,19 +286,30 @@ impl Server { offset } + // 516 primitives have no `vars` list, so their fields are spelled out here. + // a callee only reads while its proc is still running + const fn primitive_fields(value: &Value) -> Option<&'static [&'static str]> { + match value.raw.tag { + ValueTag::Vector => Some(&["x", "y", "z", "len", "size"]), + ValueTag::PixLoc => Some(&["x", "y", "z", "step_x", "step_y", "loc"]), + ValueTag::Callee => Some(&["proc", "file", "line", "src", "usr", "args", "caller"]), + _ => None + } + } + fn is_object(value: &Value) -> bool { // Hack for globals if value.raw.tag == ValueTag::World && unsafe { value.raw.data.id == 1 } { return true; } - value.get(byond_string!("vars")).is_ok() + Self::primitive_fields(value).is_some() || value.get(byond_string!("vars")).is_ok() } fn stringify(value: &Value) -> String { if List::is_list(value) { match List::from_value(value) { - Ok(list) => format!("/list {{len = {}}}", list.len()), + Ok(list) => format!("/{} {{len = {}}}", if list.is_alist() { "alist" } else { "list" }, list.len()), Err(Runtime { message }) => format!("/list (failed to get len: {:?})", message) } } else { @@ -319,6 +347,22 @@ impl Server { fn list_to_variables(&mut self, value: &Value) -> Result, Runtime> { let state = self.state.as_ref().unwrap(); let list = List::from_value(value)?; + + // numbers are keys in an alist, so the position walk below would read + // the wrong entries + if list.is_alist() { + return Ok(list + .alist_pairs()? + .into_iter() + .enumerate() + .map(|(i, (key, value))| Variable { + name: format!("[{}]", i + 1), + value: format!("{} = {}", Self::stringify(&key), Self::stringify(&value)), + variables: Some(state.get_ref(Variables::ListPair { key, value })) + }) + .collect()); + } + let len = list.len(); let mut variables = vec![]; @@ -349,8 +393,15 @@ impl Server { } fn object_to_variables(&mut self, value: &Value) -> Result, Runtime> { - // Grab `value.vars`. We have a little hack for globals which use a special - // type. + if let Some(fields) = Self::primitive_fields(value) { + return fields + .iter() + .map(|&name| Ok(self.value_to_variable(name.to_owned(), &value.get(StringRef::new(name)?)?))) + .collect(); + } + + // Grab `value.vars`. We have a little hack for globals which use a + // special type. let vars = List::from_value(&unsafe { if value.raw.tag == ValueTag::World && value.raw.data.id == 1 { Value::new(ValueTag::GlobalVars, ValueData { id: 0 }) @@ -360,7 +411,8 @@ impl Server { })?; let mut variables = vec![]; - let mut top_variables = vec![]; // These fields get displayed on top of all others + let mut top_variables = vec![]; // These fields get displayed on top of + // all others for i in 1..=vars.len() { let name = vars.get(i)?.as_string()?; @@ -382,10 +434,7 @@ impl Server { fn get_stack(&self, stack_id: u32) -> Option<&Vec> { let stack_id = stack_id as usize; - let stacks = match &self.state { - Some(state) => &state.stacks, - None => return None - }; + let stacks = &self.state.as_ref()?.stacks; if stack_id == 0 { return Some(&stacks.active); @@ -416,10 +465,7 @@ impl Server { fn get_stack_frame(&self, frame_index: u32) -> Option<&debug::StackFrame> { let mut frame_index = frame_index as usize; - let stacks = match &self.state { - Some(state) => &state.stacks, - None => return None - }; + let stacks = &self.state.as_ref()?.stacks; if frame_index < stacks.active.len() { return Some(&stacks.active[frame_index]); @@ -502,9 +548,13 @@ impl Server { match hook_instruction(&proc, instruction.offset) { Ok(()) => { - if let Some(condition) = condition { - self.conditional_breakpoints.insert((proc.id, instruction.offset as u16), condition); - } + // The client re-sends breakpoints it already set, and no + // condition this time means the old one is gone + let key = (proc.id, instruction.offset as u16); + match condition { + Some(condition) => self.conditional_breakpoints.insert(key, condition), + None => self.conditional_breakpoints.remove(&key) + }; self.send_or_disconnect(Response::BreakpointSet { result: BreakpointSetResult::Success { line } @@ -523,9 +573,7 @@ impl Server { let proc = match auxtools::Proc::find_override(instruction.proc.path, instruction.proc.override_id) { Some(proc) => proc, None => { - self.send_or_disconnect(Response::BreakpointSet { - result: BreakpointSetResult::Failed - }); + self.send_or_disconnect(Response::BreakpointUnset { success: false }); return; } }; @@ -833,8 +881,9 @@ impl Server { let result = match proc.call(&arg_values) { Ok(res) => { if let Ok(list) = res.as_list() { - // The rest are the potentially mutated parameters. We need to commit them to - // the function that called us. TODO: This sucks, obviously. + // The rest are the potentially mutated parameters. We need + // to commit them to the function that + // called us. TODO: This sucks, obviously. let len = list.len(); for i in 2..=len { let value = list.get(i).unwrap(); @@ -929,23 +978,14 @@ impl Server { fn handle_disassemble(&mut self, path: &str, id: u32) -> String { match auxtools::Proc::find_override(path, id) { Some(proc) => { - // Make sure to temporarily remove all breakpoints in this proc - let breaks = get_hooked_offsets(&proc); - - for offset in &breaks { - unhook_instruction(&proc, *offset).unwrap(); - } - - let bytecode = unsafe { proc.bytecode() }; + // The live bytecode has our breakpoints patched into it, which + // nobody wants to read + let bytecode = original_bytecode(&proc); let mut env = crate::DisassembleEnv; - let (nodes, error) = dmasm::disassembler::disassemble(bytecode, &mut env); + let (nodes, error) = dmasm::disassembler::disassemble(&bytecode, &mut env); let dism = dmasm::format_disassembly(&nodes, None); - for offset in &breaks { - hook_instruction(&proc, *offset).unwrap(); - } - match error { Some(error) => { format!("Dism for {:?}\n{}\n\tError: {:?}", proc, dism, error) @@ -1022,25 +1062,28 @@ impl Server { false } - fn check_connected(&mut self) -> bool { - match &self.stream { - ServerStream::Disconnected => false, - ServerStream::Connected(_) => true, - ServerStream::Waiting(receiver) => { - if let Ok(stream) = receiver.try_recv() { - self.stream = ServerStream::Connected(stream); - true - } else { - false + /// Applies whatever the networking thread has reported and hands back the + /// next request. `None` means nothing is waiting, or when blocking, that + /// the debug-client is gone. + fn recv_request(&mut self, block: bool) -> Option { + loop { + let event = if block { self.events.recv().ok()? } else { self.events.try_recv().ok()? }; + + match event { + Event::Connected(stream) => self.stream = Some(stream), + + Event::Disconnected => { + self.disconnect(); + + if block { + return None; + } } - } - } - } - fn wait_for_connection(&mut self) { - if let ServerStream::Waiting(receiver) = &self.stream { - if let Ok(stream) = receiver.recv() { - self.stream = ServerStream::Connected(stream); + // Sent by a client we've since given up on + Event::Request(_) if self.stream.is_none() => {} + + Event::Request(request) => return Some(request) } } } @@ -1049,22 +1092,30 @@ impl Server { let message = message.into(); eprintln!("Debug Server: {:?}", message); - if !self.check_connected() { - return; - } - self.send_or_disconnect(Response::Notification { message }); } + /// Something in here panicked. We don't know what state that left the + /// session in, so the debug-client gets dropped and can connect again. + pub fn recover_from_panic(&mut self) { + eprintln!("Debug server panicked, dropping the debug client"); + self.in_eval = false; + // A panic mid-pause skips the cleanup at the end of handle_breakpoint. + // The frames in here point at procs that are about to carry on running. + self.state = None; + self.disconnect(); + } + pub fn handle_breakpoint(&mut self, _ctx: *mut raw_types::procs::ExecutionContext, reason: BreakpointReason) -> ContinueKind { // Ignore all breakpoints unless we're connected - if !self.check_connected() || (matches!(reason, BreakpointReason::Runtime(_)) && !self.should_catch_runtimes) { + if self.stream.is_none() || (matches!(reason, BreakpointReason::Runtime(_)) && !self.should_catch_runtimes) { return ContinueKind::Continue; } self.state = Some(State::new()); - // Exit now if this is a conditional breakpoint and the condition doesn't pass! + // Exit now if this is a conditional breakpoint and the condition + // doesn't pass! if reason == BreakpointReason::Breakpoint { let proc = unsafe { (*(*(*_ctx).proc_instance())).proc }; let offset = unsafe { *(*_ctx).bytecode_offset() }; @@ -1078,8 +1129,8 @@ impl Server { } } - // We might have just executed some code so invalidate the stacks we already - // fetched + // We might have just executed some code so invalidate the + // stacks we already fetched self.state.as_mut().unwrap().invalidate_stacks(); } } @@ -1087,7 +1138,13 @@ impl Server { self.notify(format!("Pausing execution (reason: {:?})", reason)); self.send_or_disconnect(Response::BreakpointHit { reason }); - while let Ok(request) = self.requests.recv() { + // A failed send drops the client without an event to tell us so, hence + // checking the stream as well + while self.stream.is_some() { + let Some(request) = self.recv_request(true) else { + break; + }; + // Hijack and handle any Continue requests if let Request::Continue { kind } = request { self.send_or_disconnect(Response::Ack); @@ -1113,15 +1170,11 @@ impl Server { // returns true if we need to pause pub fn process(&mut self) -> bool { - // Don't do anything until we're connected - if !self.check_connected() { - return false; - } - let mut should_pause = false; - while let Ok(request) = self.requests.try_recv() { - should_pause = should_pause || self.handle_request(request); + while let Some(request) = self.recv_request(false) { + // Not `||`, that would skip every request after a Pause + should_pause |= self.handle_request(request); } should_pause @@ -1130,9 +1183,7 @@ impl Server { /// Block while processing all received requests normally until the debug /// client is configured pub fn process_until_configured(&mut self) { - self.wait_for_connection(); - - while let Ok(request) = self.requests.recv() { + while let Some(request) = self.recv_request(true) { if let Request::Configured = request { self.send_or_disconnect(Response::Ack); break; @@ -1143,87 +1194,131 @@ impl Server { } fn send_or_disconnect(&mut self, response: Response) { - match self.stream { - ServerStream::Connected(_) => match self.send(response) { - Ok(_) => {} - Err(e) => { - eprintln!("Debug server failed to send message: {}", e); - self.disconnect(); - } - }, + // Nobody to tell. An earlier send on this connection failed, or the + // client left while we were busy. + let Some(stream) = &mut self.stream else { + return; + }; - ServerStream::Waiting(_) | ServerStream::Disconnected => { - unreachable!("Debug Server is not connected") - } + if let Err(e) = Self::send(stream, &response) { + eprintln!("Debug server failed to send message: {}", e); + self.disconnect(); } } fn disconnect(&mut self) { - if let ServerStream::Connected(stream) = &mut self.stream { + if let Some(mut stream) = self.stream.take() { eprintln!("Debug server disconnecting"); - let data = bincode::serialize(&Response::Disconnect).unwrap(); - let _ = stream.write_all(&(data.len() as u32).to_le_bytes()); - let _ = stream.write_all(&data[..]); - let _ = stream.flush(); + let _ = Self::send(&mut stream, &Response::Disconnect); let _ = stream.shutdown(std::net::Shutdown::Both); + + // On Windows a shutdown stops later reads, but the one the + // networking thread is already sat in waits for the client to hang + // up too. Server::drop joins that thread, so don't leave it up to + // the client. + #[cfg(windows)] + unsafe { + use std::os::windows::io::AsRawSocket; + winapi::um::ioapiset::CancelIoEx(stream.as_raw_socket() as _, std::ptr::null_mut()); + } } - self.stream = ServerStream::Disconnected; + // Whoever connects next sets up their own breakpoints. Until then the + // old ones would only slow down the procs they sit in. + self.conditional_breakpoints.clear(); + self.should_catch_runtimes = true; + unhook_all(); } - fn send(&mut self, response: Response) -> Result<(), Box> { - if let ServerStream::Connected(stream) = &mut self.stream { - let data = bincode::serialize(&response)?; - stream.write_all(&(data.len() as u32).to_le_bytes())?; - stream.write_all(&data[..])?; - stream.flush()?; - return Ok(()); - } - - unreachable!(); + fn send(stream: &mut TcpStream, response: &Response) -> Result<(), Box> { + let data = bincode::serialize(response)?; + stream.write_all(&(data.len() as u32).to_le_bytes())?; + stream.write_all(&data[..])?; + stream.flush()?; + Ok(()) } } impl Drop for Server { fn drop(&mut self) { self.disconnect(); + + let Some(listen_addr) = self.listen_addr else { + return; + }; + + // The listener thread sits in accept() holding the port, and after a + // world reboot the next Server wants that port. Connecting is the only + // way to get it out of accept() so it can see the stop flag. + self.stop.store(true, Ordering::Relaxed); + if TcpStream::connect(listen_addr).is_err() { + return; + } + + // A debug-client the thread accepted but we never got told about is + // still in the queue, and the thread is stuck reading from it. Hang up + // on whoever turns up. The channel closes once the thread is gone. + while let Ok(event) = self.events.recv() { + if let Event::Connected(stream) = event { + self.stream = Some(stream); + self.disconnect(); + } + } + + if let Some(thread) = self.thread.take() { + let _ = thread.join(); + } } } impl ServerThread { - fn spawn_listener(self, listener: TcpListener, connection_sender: mpsc::Sender) -> JoinHandle<()> { - thread::spawn(move || match listener.accept() { - Ok((stream, _)) => { - match connection_sender.send(stream.try_clone().unwrap()) { - Ok(_) => {} - Err(e) => { - eprintln!("Debug server thread failed to pass cloned TcpStream: {}", e); - return; - } + fn spawn_listener(self, listener: TcpListener, stop: Arc) -> JoinHandle<()> { + thread::spawn(move || loop { + let stream = match listener.accept() { + Ok((stream, _)) => stream, + + Err(e) => { + eprintln!("Debug server failed to accept connection: {}", e); + return; } + }; - self.run(stream); + // That was Server::drop knocking, not a debug-client + if stop.load(Ordering::Relaxed) { + return; } - Err(e) => { - eprintln!("Debug server failed to accept connection: {}", e); + let cloned_stream = match stream.try_clone() { + Ok(cloned_stream) => cloned_stream, + + Err(e) => { + eprintln!("Debug server thread failed to clone TcpStream: {}", e); + continue; + } + }; + + if let Err(e) = self.events.send(Event::Connected(cloned_stream)) { + eprintln!("Debug server thread failed to pass cloned TcpStream: {}", e); + return; } + + self.run(stream); }) } // returns true if we should disconnect - fn handle_request(&mut self, data: &[u8]) -> Result> { + fn handle_request(&self, data: &[u8]) -> Result> { let request = bincode::deserialize::(data)?; if let Request::Disconnect = request { return Ok(true); } - self.requests.send(request)?; + self.events.send(Event::Request(request))?; Ok(false) } - fn run(mut self, mut stream: TcpStream) { + fn run(&self, mut stream: TcpStream) { let mut buf = vec![]; // The incoming stream is a u32 followed by a bincode-encoded Request. @@ -1238,6 +1333,11 @@ impl ServerThread { } }; + if len > MAX_REQUEST_LEN { + eprintln!("Debug server thread got a {} byte request, which is too big to be real", len); + break; + } + buf.resize(len as usize, 0); match stream.read_exact(&mut buf) { Ok(_) => (), @@ -1263,6 +1363,55 @@ impl ServerThread { } } - eprintln!("Debug server thread finished"); + // We can stop reading for reasons the other end doesn't know about + let _ = stream.shutdown(std::net::Shutdown::Both); + let _ = self.events.send(Event::Disconnected); + + eprintln!("Debug client connection closed"); + } +} + +#[cfg(test)] +mod tests { + use std::{ + net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream}, + sync::mpsc, + thread, + time::Duration + }; + + use super::Server; + + #[test] + fn drop_stops_a_listener_on_a_port_the_os_picked() { + let server = Server::listen(&SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).unwrap(); + // The listener thread holds the only other copy, until it exits + let stop = server.stop.clone(); + + drop(server); + assert_eq!(std::sync::Arc::strong_count(&stop), 1, "the listener thread outlived the server"); + } + + #[test] + fn drop_hangs_up_on_a_client_it_never_processed() { + let port = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap().local_addr().unwrap().port(); + let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, port)); + let (done, dropped) = mpsc::channel(); + + // A hung drop never returns, so it gets its own thread to be stuck on + thread::spawn(move || { + let server = Server::listen(&addr).unwrap(); + let _client = TcpStream::connect(addr).unwrap(); + // Long enough for the networking thread to start reading from the + // client. Nothing here tells the server it connected. + thread::sleep(Duration::from_millis(200)); + + drop(server); + let _ = done.send(()); + }); + + dropped + .recv_timeout(Duration::from_secs(5)) + .expect("dropping the server hung on a client that was still connected"); } } diff --git a/debug_server/src/stddef.rs b/debug_server/src/stddef.rs index 1cc95143..72a70ac8 100644 --- a/debug_server/src/stddef.rs +++ b/debug_server/src/stddef.rs @@ -1,61 +1,25 @@ use std::{ - ffi::{c_void, CStr, CString}, + ffi::{c_void, CStr}, os::raw::c_char }; -use auxtools::*; +use auxtools::{byond_scan::find_export, *}; #[cfg(windows)] -static STDDEF_FN_SYMBOL: &[u8] = b"?StdDefDM@DungBuilder@@QAEPADXZ\0"; +const STDDEF_FN_SYMBOL: &CStr = c"?StdDefDM@DungBuilder@@QAEPADXZ"; #[cfg(unix)] -static STDDEF_FN_SYMBOL: &[u8] = b"_ZN11DungBuilder8StdDefDMEv\0"; +const STDDEF_FN_SYMBOL: &CStr = c"_ZN11DungBuilder8StdDefDMEv"; static mut STDDEF: Option<&'static str> = None; #[init(full)] fn stddef_init() -> Result<(), String> { - let stddef_fn: extern "C" fn(*const c_void) -> *const c_char; - - #[cfg(windows)] - { - use winapi::um::libloaderapi; - - unsafe { - let mut module = std::ptr::null_mut(); - let core_str = CString::new(BYONDCORE).unwrap(); - if libloaderapi::GetModuleHandleExA(0, core_str.as_ptr(), &mut module) == 0 { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = libloaderapi::GetProcAddress(module, STDDEF_FN_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); - } - - stddef_fn = std::mem::transmute(symbol); - } - } - - #[cfg(unix)] - { - use libc::{dlopen, dlsym, RTLD_LAZY}; - - unsafe { - let byond_core_str = CString::new(BYONDCORE).unwrap(); - let module = dlopen(byond_core_str.as_ptr(), RTLD_LAZY); - if module.is_null() { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = dlsym(module, STDDEF_FN_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); - } - - stddef_fn = std::mem::transmute(symbol); - } + let symbol = find_export(STDDEF_FN_SYMBOL); + if symbol.is_null() { + return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); } + let stddef_fn: extern "C" fn(*const c_void) -> *const c_char = unsafe { std::mem::transmute(symbol) }; unsafe { match CStr::from_ptr(stddef_fn(std::ptr::null())).to_str() { diff --git a/instruction_hooking/Cargo.toml b/instruction_hooking/Cargo.toml index aa227199..eb442608 100644 --- a/instruction_hooking/Cargo.toml +++ b/instruction_hooking/Cargo.toml @@ -15,14 +15,6 @@ cc = "1.0" auxtools = { path = "../auxtools" } dmasm = { workspace = true } retour = { workspace = true } -symbolic-common = "12" -symbolic-demangle = { version = "12", default-features = false } - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "libloaderapi", "errhandlingapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" [lints] workspace = true diff --git a/instruction_hooking/src/execute_instruction_hook.unix.S b/instruction_hooking/src/execute_instruction_hook.unix.S index 2172553b..2916fea7 100644 --- a/instruction_hooking/src/execute_instruction_hook.unix.S +++ b/instruction_hooking/src/execute_instruction_hook.unix.S @@ -4,15 +4,16 @@ .extern execute_instruction_original .extern handle_instruction -// EDI = [CURRENT_EXECUTION_CONTEXT] +// ESI = [CURRENT_EXECUTION_CONTEXT] execute_instruction_hook: // Give rust a chance to handle the instruction. Leaves [CURRENT_EXECUTION_CONTEXT] in EAX. + // The 16 bytes pushed here keep ESP 16-byte aligned at the call. SUB ESP, 0x04 PUSH ECX PUSH EDX - PUSH EDI + PUSH ESI call handle_instruction - MOV EDI, EAX + MOV ESI, EAX ADD ESP, 0x04 POP EDX POP ECX diff --git a/instruction_hooking/src/lib.rs b/instruction_hooking/src/lib.rs index e60f1449..cbd0b4dd 100644 --- a/instruction_hooking/src/lib.rs +++ b/instruction_hooking/src/lib.rs @@ -1,25 +1,31 @@ pub mod disassemble_env; -use auxtools::*; +use auxtools::{ + byond_scan::{Anchor, Extract, Recipe, SignatureTreatment, VersionRange}, + * +}; use retour::RawDetour; use std::{any::Any, cell::UnsafeCell, ffi::c_void}; +// Where the interpreter reads the next opcode, inside exec_proc. Copied from +// `byond_catalog` in the byond-re repo, which checks it against every local +// build. +// +// The running proc's frame sits in EDI here and in ESI on Linux, so each +// platform has its own assembly in execute_instruction_hook.*. #[cfg(windows)] -signatures! { - execute_instruction => version_dependent_signature!( - 1616.. => "0F B7 47 ?? 8B 4F ?? 8B F0 8B 14 ?? 89 95 ?? ?? ?? ?? 81 FA ?? 01 00 00", - 1590..1616 => "0F B7 48 ?? 8B ?? ?? 8B F1 8B ?? ?? 81 ?? ?? ?? 00 00 0F 87 ?? ?? ?? ??", - ..1590 => "0F B7 48 ?? 8B 78 ?? 8B F1 8B 14 ?? 81 FA ?? ?? 00 00 0F 87 ?? ?? ?? ??" - ) -} - +const DISPATCH_MASK: &str = "0F B7 47 ?? 8B 4F ?? 8B F0 8B 14 ?? 89 95 ?? ?? ?? ?? 81 FA ?? 01 00 00"; #[cfg(unix)] -signatures! { - execute_instruction => version_dependent_signature!( - 1616.. => "0F B7 C0 8D 14 ?? 8B 02 8B 52 ?? 8B 4E ?? 8B 5E ?? 89 46 ?? 89 56 ?? 89 0C 24", - ..1616 => "0F B7 47 ?? 8B 57 ?? 0F B7 D8 8B 0C ?? 81 F9 ?? ?? 00 00 77 ?? FF 24 8D ?? ?? ?? ??" - ) -} +const DISPATCH_MASK: &str = "0F B7 46 ?? 8B 56 ?? 66 89 85 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? C1 E0 02 89 85 ?? ?? ?? ?? 01 D0 8B 18 81 FB ?? 01 00 00 \ + 0F 87 ?? ?? ?? ?? FF 24 9D ?? ?? ?? ??"; + +const EXECUTE_INSTRUCTION: Recipe = Recipe { + name: "execute_instruction", + versions: VersionRange { min: 1659, max: 1688 }, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, DISPATCH_MASK), + hops: &[], + extract: Extract::Entry +}; // stackoverflow copypasta https://old.reddit.com/r/rust/comments/kkap4e/how_to_cast_a_boxdyn_mytrait_to_an_actual_struct/ pub trait InstructionHookToAny: 'static { @@ -42,8 +48,8 @@ extern "C" { // Trampoline to the original un-hooked BYOND execute_instruction code static mut execute_instruction_original: *const c_void; - // Our version of execute_instruction. It hasn't got a calling convention rust - // knows about, so don't call it. + // Our version of execute_instruction. It hasn't got a calling convention + // rust knows about, so don't call it. fn execute_instruction_hook(); // The 514 version of the instruction hook. @@ -53,11 +59,7 @@ extern "C" { #[init(full)] fn instruction_hooking_init() -> Result<(), String> { - let byondcore = sigscan::Scanner::for_module(BYONDCORE).unwrap(); - - find_signatures_result! { byondcore, - execute_instruction - } + let execute_instruction = find_recipe(&EXECUTE_INSTRUCTION)?; #[cfg(windows)] let versioned_hook = if auxtools::version::get().0 == 514 { diff --git a/tests/auxtest/src/hooks.rs b/tests/auxtest/src/hooks.rs new file mode 100644 index 00000000..baab4339 --- /dev/null +++ b/tests/auxtest/src/hooks.rs @@ -0,0 +1,55 @@ +use auxtools::*; + +const DISCARDED: &str = "a hook result nobody is going to look at"; + +#[hook("/datum/auxtest_discard/New")] +fn discard_new() { + Value::from_string(DISCARDED) +} + +#[hook("/proc/auxtest_hooked_global")] +fn hooked_global(x: Value) { + Ok(Value::from(x.as_number()? + 1.0)) +} + +#[hook("/datum/auxtest_hooked/proc/hooked_method")] +fn hooked_method(x: Value) { + Ok(Value::from(x.as_number()? * 2.0)) +} + +#[hook("/proc/auxtest_hooks")] +fn test_hooks() { + use raw_types::{funcs, strings}; + + // held for the whole test so the string can't be freed and remade under us + let held = StringRef::new(DISCARDED)?; + + unsafe { + let mut entry: *mut strings::StringEntry = std::ptr::null_mut(); + assert_eq!(funcs::get_string_table_entry(&mut entry, held.get_id()), 1); + + // BYOND runs New() with the "don't want the result" bit set and never + // releases what comes back, so the hook's result has to be dropped for + // it + let before = (*entry).ref_count; + Proc::find("/proc/auxtest_new_discard").unwrap().call(&[])?; + if (*entry).ref_count != before { + return Err(runtime!("test_hooks: a discarded hook result kept its reference")); + } + } + + // calling a proc we hooked ourselves has to land in the hook, by id and by + // name + let by_id = Proc::find("/proc/auxtest_hooked_global").unwrap().call(&[&Value::from(41.0)])?; + if by_id.as_number()? != 42.0 { + return Err(runtime!("test_hooks: calling a hooked global proc from Rust did not reach the hook")); + } + + let datum = Proc::find("/proc/auxtest_new_hooked").unwrap().call(&[])?; + let by_name = datum.call("hooked_method", &[&Value::from(21.0)])?; + if by_name.as_number()? != 42.0 { + return Err(runtime!("test_hooks: calling a hooked datum proc from Rust did not reach the hook")); + } + + Ok(Value::from(true)) +} diff --git a/tests/auxtest/src/lib.rs b/tests/auxtest/src/lib.rs index eda00c56..7dbd0784 100644 --- a/tests/auxtest/src/lib.rs +++ b/tests/auxtest/src/lib.rs @@ -1,6 +1,10 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use auxtools::*; +mod hooks; mod lists; +mod procs; mod strings; mod value_from; mod weak; @@ -15,8 +19,24 @@ fn inc_counter() { })) } -#[hook("/proc/auxtest_out")] -fn out(msg: Value) { - eprintln!("\n{}", msg.as_string()?); +// Not a hook, so a failure still gets reported when hooks are what broke +byond_ffi_fn! { auxtest_out(msg) { + eprintln!("\n{}", msg); + Some(String::new()) +} } + +static FAIL_NEXT_INIT: AtomicBool = AtomicBool::new(false); + +#[hook("/proc/auxtest_fail_next_init")] +fn fail_next_init() { + FAIL_NEXT_INIT.store(true, Ordering::Relaxed); Ok(Value::NULL) } + +#[init(partial)] +fn fail_init_on_request() -> Result<(), String> { + if FAIL_NEXT_INIT.load(Ordering::Relaxed) { + return Err("auxtest asked for this init to fail".to_owned()); + } + Ok(()) +} diff --git a/tests/auxtest/src/lists.rs b/tests/auxtest/src/lists.rs index 4160a735..972190c5 100644 --- a/tests/auxtest/src/lists.rs +++ b/tests/auxtest/src/lists.rs @@ -52,5 +52,57 @@ fn test_lists() { } } + // is_list should agree with DM's own islist() on everything, and len() with + // length() on everything that is a list + let samples = Proc::find("/proc/auxtest_islist_samples").unwrap().call(&[])?.as_list()?; + let dm_islist = Proc::find("/proc/auxtest_islist").unwrap(); + let dm_length = Proc::find("/proc/auxtest_length").unwrap(); + for n in 1..=samples.len() { + let sample = samples.get(n)?; + let expected = dm_islist.call(&[&sample])?.as_number()? != 0.0; + if List::is_list(&sample) != expected { + return Err(runtime!("test_lists: is_list disagrees with islist() on sample {}", n)); + } + if expected && sample.as_list()?.len() as f32 != dm_length.call(&[&sample])?.as_number()? { + return Err(runtime!("test_lists: len disagrees with length() on sample {}", n)); + } + } + + let sample = Proc::find("/proc/auxtest_alist_sample").unwrap().call(&[])?; + let alist = sample.as_list()?; + if !alist.is_alist() || list_a.is_alist() { + return Err(runtime!("test_lists: is_alist is wrong")); + } + if alist.get(byond_string!("a"))?.as_number()? != 1.0 { + return Err(runtime!("test_lists: alist[\"a\"] != 1")); + } + // a number is a key here, not a position + if alist.get(7)?.as_string()? != "seven" { + return Err(runtime!("test_lists: alist[7] != \"seven\"")); + } + + let keys = Proc::find("/proc/auxtest_alist_keys").unwrap().call(&[&sample])?.as_list()?; + let pairs = alist.alist_pairs()?; + if pairs.len() != 3 || pairs.len() != keys.len() as usize { + return Err(runtime!("test_lists: alist_pairs found {} pairs, not 3", pairs.len())); + } + for (n, (key, value)) in pairs.iter().enumerate() { + if *key != keys.get(n as u32 + 1)? { + return Err(runtime!("test_lists: alist_pairs key {} is out of DM's order", n + 1)); + } + if *value != alist.get(key)? { + return Err(runtime!("test_lists: alist_pairs value {} != alist[key]", n + 1)); + } + } + + alist.set(8, byond_string!("eight"))?; + if alist.get(8)?.as_string()? != "eight" || alist.len() != 4 { + return Err(runtime!("test_lists: set on an alist didn't stick")); + } + + if list_a.alist_pairs().is_ok() { + return Err(runtime!("test_lists: alist_pairs accepted a plain list")); + } + Ok(Value::from(true)) } diff --git a/tests/auxtest/src/procs.rs b/tests/auxtest/src/procs.rs new file mode 100644 index 00000000..f125627b --- /dev/null +++ b/tests/auxtest/src/procs.rs @@ -0,0 +1,33 @@ +use auxtools::{raw_types::procs::ProcId, *}; + +// BYOND moves its whole proc table when the table grows. A `Proc` we were +// already holding has to keep working after the move. +#[hook("/proc/auxtest_procs")] +fn test_procs() { + let table_start = || Proc::from_id(ProcId(0)).unwrap().entry(); + + let before: Vec<(Proc, Vec)> = (0..) + .map_while(|id| Proc::from_id(ProcId(id))) + .map(|proc| { + let bytecode = unsafe { proc.bytecode() }.to_vec(); + (proc, bytecode) + }) + .collect(); + let old_start = table_start(); + + Proc::find("/proc/auxtest_grow_proc_table") + .ok_or_else(|| runtime!("test_procs: /proc/auxtest_grow_proc_table not defined"))? + .call(&[])?; + + if table_start() == old_start { + return Err(runtime!("test_procs: the proc table never moved, so this checked nothing")); + } + + for (proc, bytecode) in before { + if unsafe { proc.bytecode() } != bytecode { + return Err(runtime!("test_procs: {} reads different bytecode after the proc table moved", proc.path)); + } + } + + Ok(Value::from(true)) +} diff --git a/tests/auxtest/src/strings.rs b/tests/auxtest/src/strings.rs index fc5ca5f3..1158a4e7 100644 --- a/tests/auxtest/src/strings.rs +++ b/tests/auxtest/src/strings.rs @@ -30,8 +30,8 @@ fn test_strings() { return Err(runtime!("test_string: string_a's reference count != 0")); } - // Creating a value from our strings should result in both having a reference - // count of 1. + // Creating a value from our strings should result in both having a + // reference count of 1. let value_a = Value::new(values::ValueTag::String, values::ValueData { string: string_a }); let value_b = Value::new(values::ValueTag::String, values::ValueData { string: string_b }); @@ -46,8 +46,8 @@ fn test_strings() { let concatenated = Proc::find("/proc/concat_strings").unwrap().call(&[&value_a, &value_b])?; - // Returned value should be equal to string_a_contents .. string_b_contents - // and have a ref count of 1 + // Returned value should be equal to string_a_contents .. + // string_b_contents and have a ref count of 1 if concatenated.raw.tag != values::ValueTag::String { return Err(runtime!("test_string: concat_strings did not return a string")); } @@ -66,8 +66,8 @@ fn test_strings() { return Err(runtime!("test_string: expected_concat != actual_concat")); } - // The strings should still have a reference count of 1 after concat_strings has - // used them + // The strings should still have a reference count of 1 after + // concat_strings has used them if (*string_a_entry).ref_count != 1 { return Err(runtime!("test_string: string_a's reference count != 1 after concat_strings")); } diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index e1b90976..11f88018 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -11,12 +11,30 @@ /proc/auxtools_stack_trace(msg) CRASH(msg) -/proc/auxtest_out() - // Graceful failure +// Goes straight to the library instead of through a hook, so a failure still +// gets reported when hooks are what broke +/proc/auxtest_out(msg) + call_ext(auxtools_test_dll(), "auxtest_out")(msg) /proc/auxtest_inc_counter() CRASH() +/proc/auxtest_fail_next_init() + CRASH() + +// A failed init has to stay failed, and leave none of its hooks behind +/proc/auxtest_failed_init(auxtest_dll) + var/first = call_ext(auxtest_dll, "auxtools_init")() + ASSERT(findtext(first, "FAILED") == 1) + ASSERT(call_ext(auxtest_dll, "auxtools_init")() == first) + + var/reached_hook = TRUE + try + auxtest_hooks() + catch + reached_hook = FALSE + ASSERT(!reached_hook) + /proc/concat_strings(a, b) return addtext(a, b) @@ -30,10 +48,76 @@ var/datum/weak_test_datum /proc/create_datum_for_weak() weak_test_datum = new +/mob/verb/auxtest_verb() + return + +// A spread of values for `List::is_list` to agree with `islist()` on: plain +// lists, the special lists that hang off an object, and things that are not +// lists at all. `M.filters[1]` is the mean one, since a single filter has the +// same tag as the filters list it came out of. +/proc/auxtest_islist_samples() + var/mob/M = new + M.filters += filter(type = "blur") + var/savefile/F = new + var/list/L = list(1, 2) + return list( + null, 1, "string", M, /list, &L, + L, alist("a" = 1), L.vars, args, + M.verbs, M.contents, M.vars, M.overlays, M.group, M.vis_contents, + M.filters, M.filters[1], + F.dir, world.contents, world.vars, global.vars, + ) + +// 7 is a key here and not a position, and "n" has a null value on purpose +/proc/auxtest_alist_sample() + return alist("a" = 1, 7 = "seven", "n" = null) + +/proc/auxtest_alist_keys(alist/A) + var/list/keys = list() + for (var/k in A) + keys += list(k) + return keys + +/proc/auxtest_islist(value) + return islist(value) + +/proc/auxtest_length(value) + return length(value) + +// New() is hooked and hands back a string that nothing here ever reads +/datum/auxtest_discard/New() + CRASH() + +/proc/auxtest_new_discard() + new /datum/auxtest_discard + +// Both hooked, and then called from Rust by the same library that hooked them +/proc/auxtest_hooked_global(x) + CRASH() + +/datum/auxtest_hooked/proc/hooked_method(x) + CRASH() + +/proc/auxtest_new_hooked() + return new /datum/auxtest_hooked + +// `new` on a verb path with a name BYOND has not seen yet adds one entry to its +// proc table, and the whole table moves when it grows +/proc/auxtest_grow_proc_table() + var/mob/M = new + for (var/i in 1 to 4096) + new /mob/verb/auxtest_verb(M, "auxtest clone [i]") + // Tests +/proc/auxtest_hooks() + CRASH() + /proc/auxtest_lists() CRASH() +/proc/auxtest_procs() + CRASH() + /proc/auxtest_strings() CRASH() @@ -50,7 +134,9 @@ var/datum/weak_test_datum ASSERT(init_result == "SUCCESS") // Tests + ASSERT(auxtest_hooks() == TRUE) ASSERT(auxtest_lists() == TRUE) + ASSERT(auxtest_procs() == TRUE) ASSERT(auxtest_strings() == TRUE) ASSERT(auxtest_value_from() == TRUE) @@ -60,6 +146,11 @@ var/datum/weak_test_datum // Stop testing after the 8th reboot if (auxtest_inc_counter() == 8) + // last, because nothing can init again in this process afterwards + auxtest_fail_next_init() + call_ext(auxtest_dll, "auxtools_shutdown")() + auxtest_failed_init(auxtest_dll) + auxtest_out("SUCCESS: Finished") call_ext(auxtest_dll, "auxtools_shutdown")() shutdown() diff --git a/tests/debug_test/Cargo.toml b/tests/debug_test/Cargo.toml new file mode 100644 index 00000000..0a0352e8 --- /dev/null +++ b/tests/debug_test/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "debug_test" +version = "0.1.0" +publish = false +authors.workspace = true +edition.workspace = true + +# Same trick as test_runner: depending on the cdylib gets debug_server +# compiled before `cargo run` runs us. +[dependencies] +debug_server = { path = "../../debug_server" } +serde = { version = "1", features = ["derive"] } +bincode = "1" + +[lints] +workspace = true diff --git a/tests/debug_test/src/client.rs b/tests/debug_test/src/client.rs new file mode 100644 index 00000000..ccfd0f7b --- /dev/null +++ b/tests/debug_test/src/client.rs @@ -0,0 +1,98 @@ +use std::{ + io::{Read, Write}, + net::{Ipv4Addr, TcpStream}, + time::{Duration, Instant} +}; + +use crate::server_types::{BreakpointReason, Request, Response}; + +// Long enough for a world reboot, short enough that a dead world fails the run +// instead of hanging it +const TIMEOUT: Duration = Duration::from_secs(30); + +/// Stands in for dm-langserver: speaks the debug server's protocol over TCP. +pub struct Client { + stream: TcpStream +} + +impl Client { + /// Keeps trying until the world is listening. + pub fn connect(port: u16) -> Self { + let start = Instant::now(); + + loop { + match TcpStream::connect((Ipv4Addr::LOCALHOST, port)) { + Ok(stream) => { + stream.set_read_timeout(Some(TIMEOUT)).unwrap(); + return Self { stream }; + } + + Err(e) => { + assert!(start.elapsed() < TIMEOUT, "couldn't connect to the debug server: {}", e); + std::thread::sleep(Duration::from_millis(100)); + } + } + } + } + + pub fn send(&mut self, request: Request) { + let data = bincode::serialize(&request).unwrap(); + self.stream.write_all(&(data.len() as u32).to_le_bytes()).unwrap(); + self.stream.write_all(&data).unwrap(); + } + + fn read_message(&mut self) -> std::io::Result { + let mut len_bytes = [0u8; 4]; + self.stream.read_exact(&mut len_bytes)?; + + let mut data = vec![0; u32::from_le_bytes(len_bytes) as usize]; + self.stream.read_exact(&mut data)?; + + Ok(bincode::deserialize(&data).unwrap()) + } + + /// The next message that isn't a notification. + pub fn recv(&mut self) -> Response { + loop { + match self.read_message().expect("debug server stopped answering") { + Response::Notification { message } => println!(" [debug server] {}", message), + response => return response + } + } + } + + pub fn request(&mut self, request: Request) -> Response { + self.send(request); + self.recv() + } + + /// Waits for the world to pause and says why it did. + pub fn expect_pause(&mut self) -> BreakpointReason { + match self.recv() { + Response::BreakpointHit { reason } => reason, + other => panic!("expected the world to pause, got {:?}", other) + } + } + + pub fn resume(&mut self) { + let response = self.request(Request::Continue { + kind: crate::server_types::ContinueKind::Continue + }); + assert!(matches!(response, Response::Ack), "expected Ack for Continue, got {:?}", response); + } + + /// Waits for the server to hang up on us. + pub fn wait_closed(mut self) { + loop { + match self.read_message() { + Ok(Response::Disconnect) => return, + Ok(_) => {} + Err(e) => { + let timed_out = matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock); + assert!(!timed_out, "debug server never hung up"); + return; + } + } + } + } +} diff --git a/tests/debug_test/src/main.rs b/tests/debug_test/src/main.rs new file mode 100644 index 00000000..14723ce8 --- /dev/null +++ b/tests/debug_test/src/main.rs @@ -0,0 +1,285 @@ +//! Runs a tiny world with the debug server loaded and pokes it the way +//! dm-langserver would, to make sure the debugger can't take the game down. + +mod client; + +// test_runner already knows how to find BYOND, and the debug server's message +// types aren't exported from its cdylib. Both are pulled in as source, and +// each has bits this test never touches. +#[allow(dead_code)] +#[path = "../../test_runner/src/paths.rs"] +mod paths; +#[allow(dead_code)] +#[path = "../../../debug_server/src/server_types.rs"] +mod server_types; + +use std::{ + net::{Ipv4Addr, TcpListener}, + path::PathBuf, + process::{Child, Command}, + time::Duration +}; + +use client::Client; +use paths::ByondCommand; +use server_types::{BreakpointReason, BreakpointSetResult, InstructionRef, ProcRef, Request, Response}; + +// The debug server names global procs without the /proc part +const BREAKPOINT_TARGET: &str = "/breakpoint_target"; +const TICK_MARKER: &str = "/tick_marker"; + +struct World(Child); + +impl Drop for World { + fn drop(&mut self) { + let _ = self.0.kill(); + let _ = self.0.wait(); + } +} + +fn find_debug_server() -> PathBuf { + let mut path = std::env::current_exe().unwrap(); + path.pop(); + + #[cfg(unix)] + path.push("libdebug_server.so"); + + #[cfg(windows)] + path.push("debug_server.dll"); + + assert!(path.is_file(), "couldn't find debug_server"); + path +} + +fn host_file(name: &str) -> PathBuf { + let mut path = std::env::current_dir().unwrap(); + path.push("tests/debug_test_host"); + path.push(name); + path +} + +fn free_port() -> u16 { + TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap().local_addr().unwrap().port() +} + +fn start_of(proc: &str) -> InstructionRef { + InstructionRef { + proc: ProcRef { + path: proc.to_owned(), + override_id: 0 + }, + offset: 0 + } +} + +fn set_breakpoint(client: &mut Client, proc: &str, condition: Option<&str>) { + let response = client.request(Request::BreakpointSet { + instruction: start_of(proc), + condition: condition.map(str::to_owned) + }); + + assert!( + matches!(response, Response::BreakpointSet { + result: BreakpointSetResult::Success { .. } + }), + "couldn't set a breakpoint on {}: {:?}", + proc, + response + ); +} + +fn eval(client: &mut Client, frame_id: Option, command: &str) -> String { + let response = client.request(Request::Eval { + frame_id, + command: command.to_owned(), + context: None + }); + + match response { + Response::Eval(eval) => eval.value, + other => panic!("expected the result of {:?}, got {:?}", command, other) + } +} + +/// The proc the world is paused in. +fn paused_in(client: &mut Client) -> String { + match client.request(Request::Stacks) { + Response::Stacks { stacks } => stacks.into_iter().next().expect("paused with no stacks").name, + other => panic!("expected Stacks, got {:?}", other) + } +} + +/// Evals `command` in `frame_id` and returns its one-line text and its expanded +/// fields as "name = value" strings. +fn eval_fields(client: &mut Client, frame_id: u32, command: &str) -> (String, Vec) { + let response = client.request(Request::Eval { + frame_id: Some(frame_id), + command: command.to_owned(), + context: None + }); + let Response::Eval(eval) = response else { + panic!("expected the result of {:?}, got {:?}", command, response); + }; + let refs = eval.variables.unwrap_or_else(|| panic!("{} should be expandable", command)); + let vars = match client.request(Request::Variables { vars: refs }) { + Response::Variables { vars } => vars, + other => panic!("expected Variables, got {:?}", other) + }; + let fields = vars.into_iter().map(|var| format!("{} = {}", var.name, var.value)).collect(); + (eval.value, fields) +} + +fn expect_breakpoint_in(client: &mut Client, proc: &str) { + assert_eq!(client.expect_pause(), BreakpointReason::Breakpoint); + assert_eq!(paused_in(client), proc); +} + +fn pause(client: &mut Client) { + let response = client.request(Request::Pause); + assert!(matches!(response, Response::Ack), "expected Ack for Pause, got {:?}", response); + assert_eq!(client.expect_pause(), BreakpointReason::Pause); +} + +fn main() { + let res = Command::new(paths::find_dm()) + .with_byond_paths() + .arg(host_file("debug_test_host.dme")) + .status() + .unwrap(); + assert!(res.success(), "dreammaker build failed"); + + let port = free_port(); + + let _world = World( + Command::new(paths::find_dreamdaemon()) + .with_byond_paths() + .env("AUXTOOLS_DEBUG_DLL", find_debug_server()) + .env("AUXTOOLS_DEBUG_PORT", port.to_string()) + .arg(host_file("debug_test_host.dmb")) + .arg("-trusted") + .arg("-close") + .spawn() + .unwrap() + ); + + let dis_command = format!("#dis {}", BREAKPOINT_TARGET); + + let mut client = Client::connect(port); + let clean_disassembly = eval(&mut client, None, &dis_command); + set_breakpoint(&mut client, BREAKPOINT_TARGET, None); + expect_breakpoint_in(&mut client, BREAKPOINT_TARGET); + println!("ok: breakpoint hit"); + + // VS Code re-runs every watch expression on every stop, so this is the + // easy one to hit by accident + assert_eq!(eval(&mut client, Some(0), "breakpoint_target()"), "2"); + println!("ok: evaluating a proc that has a breakpoint in it"); + + // numbers are keys in an alist, so walking it by position shows nonsense + let response = client.request(Request::Eval { + frame_id: Some(0), + command: "alist_sample()".to_owned(), + context: None + }); + let Response::Eval(alist) = response else { + panic!("expected the result of alist_sample(), got {:?}", response); + }; + assert_eq!(alist.value, "/alist {len = 3}"); + let vars = match client.request(Request::Variables { + vars: alist.variables.expect("an alist should be expandable") + }) { + Response::Variables { vars } => vars, + other => panic!("expected Variables, got {:?}", other) + }; + let mut shown: Vec<_> = vars.into_iter().map(|var| var.value).collect(); + shown.sort(); + assert_eq!(shown, ["\"a\" = 1", "\"n\" = null", "7 = \"seven\""]); + println!("ok: an alist shows its key/value pairs"); + + // size is a float, so only its start is checked + let (text, fields) = eval_fields(&mut client, 0, "vector_sample()"); + assert_eq!(text, "vector(1,2,3)"); + assert_eq!(fields[..4], ["x = 1", "y = 2", "z = 3", "len = 3"]); + assert!(fields[4].starts_with("size = 3.74"), "unexpected size: {}", fields[4]); + let (text, fields) = eval_fields(&mut client, 0, "vector2_sample()"); + assert_eq!(text, "vector(4,5)"); + assert_eq!(fields[..4], ["x = 4", "y = 5", "z = 0", "len = 2"]); + println!("ok: a vector shows its components"); + + let (text, fields) = eval_fields(&mut client, 0, "pixloc_sample()"); + assert_eq!(text, "pixloc(1,1,1)"); + assert_eq!(fields[..5], ["x = 1", "y = 1", "z = 1", "step_x = 0", "step_y = 0"]); + assert!(fields[5].starts_with("loc = "), "unexpected loc: {}", fields[5]); + println!("ok: a pixloc shows its position"); + + // frame 1 is main_loop, which holds a callee of itself + let (text, fields) = eval_fields(&mut client, 1, "this_proc"); + assert!(text.contains("/proc/main_loop"), "unexpected callee text: {}", text); + assert_eq!(fields[..2], ["proc = /proc/main_loop", "file = \"debug_test_host.dm\""]); + assert!(fields[2].starts_with("line = "), "unexpected line: {}", fields[2]); + assert_eq!(fields[3..6], ["src = null", "usr = null", "args = /list {len = 1}"]); + assert!(fields[6].starts_with("caller = "), "unexpected caller: {}", fields[6]); + println!("ok: a callee shows its proc"); + + assert_eq!(eval(&mut client, None, &dis_command), clean_disassembly); + println!("ok: disassembly doesn't show the breakpoint"); + + let response = client.request(Request::BreakpointUnset { + instruction: start_of("/proc/does_not_exist") + }); + assert!( + matches!(response, Response::BreakpointUnset { success: false }), + "unsetting a breakpoint on a missing proc got {:?}", + response + ); + println!("ok: unsetting a breakpoint on a missing proc"); + + // The condition is false, and working that out means calling a proc that + // has its own breakpoint. The second stop in tick_marker is the one that + // shows breakpoint_target got skipped. + set_breakpoint(&mut client, TICK_MARKER, None); + set_breakpoint(&mut client, BREAKPOINT_TARGET, Some("tick_marker() == 0")); + client.resume(); + expect_breakpoint_in(&mut client, TICK_MARKER); + client.resume(); + expect_breakpoint_in(&mut client, TICK_MARKER); + println!("ok: conditional breakpoint that calls a proc with a breakpoint"); + + // dm-langserver re-sends every breakpoint without unsetting it first, so + // this is how a condition gets removed + set_breakpoint(&mut client, BREAKPOINT_TARGET, None); + client.resume(); + expect_breakpoint_in(&mut client, BREAKPOINT_TARGET); + println!("ok: re-setting a breakpoint drops its old condition"); + + eval(&mut client, None, "start_runtimes()"); + client.resume(); + drop(client); + // start_runtimes() gives two seconds of runtime errors, wait them out + std::thread::sleep(Duration::from_secs(3)); + + let mut client = Client::connect(port); + pause(&mut client); + println!("ok: survived runtime errors with the debugger gone, and it can attach again"); + + client.resume(); + client.send(Request::Disconnect); + drop(client); + + let mut client = Client::connect(port); + let response = client.request(Request::Stacks); + assert!(matches!(response, Response::Stacks { .. }), "expected Stacks, got {:?}", response); + println!("ok: attaching again after a clean detach"); + + eval(&mut client, None, "request_reboot()"); + client.wait_closed(); + // The old world hangs up on us a moment before it stops listening. Don't + // get picked up by its last accept(). + std::thread::sleep(Duration::from_millis(500)); + + let mut client = Client::connect(port); + pause(&mut client); + println!("ok: attaching again after a world reboot"); + + println!("Debug server tests succeeded"); +} diff --git a/tests/debug_test_host/debug_test_host.dm b/tests/debug_test_host/debug_test_host.dm new file mode 100644 index 00000000..84534c2d --- /dev/null +++ b/tests/debug_test_host/debug_test_host.dm @@ -0,0 +1,82 @@ +#if DM_VERSION < 515 +#define call_ext call +#endif + +/proc/auxtools_stack_trace(msg) + CRASH(msg) + +/proc/auxtools_expr_stub() + CRASH("auxtools not loaded") + +/proc/enable_debugging(mode, port) + CRASH("auxtools not loaded") + +var/runtimes_left = 0 +var/reboot_requested = FALSE + +// The test puts its breakpoints on these two. They are called in this order, +// once per tick, so the test can tell which breakpoint it stopped on. +/proc/breakpoint_target() + return 2 + +/proc/tick_marker() + return 1 + +/proc/alist_sample() + return alist("a" = 1, 7 = "seven", "n" = null) + +/proc/vector_sample() + return vector(1, 2, 3) + +/proc/vector2_sample() + return vector(4, 5) + +/proc/pixloc_sample() + return pixloc(1, 1, 1) + +// Two seconds of runtime errors, then quiet again. The test detaches while +// they are going and comes back after they stop. +/proc/start_runtimes() + runtimes_left = 20 + +/proc/make_runtime() + var/list/nothing + return nothing.len + +/proc/request_reboot() + reboot_requested = TRUE + +/proc/main_loop(debug_dll) + // a callee only reads while its proc is running, so the test looks at this one + var/callee/this_proc = callee + while (!reboot_requested) + breakpoint_target() + tick_marker() + if (runtimes_left > 0) + runtimes_left-- + spawn() + make_runtime() + sleep(1) + + // Shutting down has to happen out here. The debugger evaluates + // request_reboot() from inside the debug server, which can't free itself. + call_ext(debug_dll, "auxtools_shutdown")() + world.Reboot() + +/world/New() + // pixloc_sample() needs a real tile to point at + maxx = 5 + maxy = 5 + maxz = 1 + + var/debug_dll = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") + var/init_result = call_ext(debug_dll, "auxtools_init")() + world.log << "init_result = [init_result]" + ASSERT(init_result == "SUCCESS") + + // No port given, so it comes from AUXTOOLS_DEBUG_PORT + enable_debugging("BACKGROUND") + + spawn() + main_loop(debug_dll) + . = ..() diff --git a/tests/debug_test_host/debug_test_host.dme b/tests/debug_test_host/debug_test_host.dme new file mode 100644 index 00000000..1cd91340 --- /dev/null +++ b/tests/debug_test_host/debug_test_host.dme @@ -0,0 +1,18 @@ +// DM Environment file for debug_test_host.dme. +// All manual changes should be made outside the BEGIN_ and END_ blocks. +// New source code should be placed in .dm files: choose File/New --> Code File. + +// BEGIN_INTERNALS +// END_INTERNALS + +// BEGIN_FILE_DIR +#define FILE_DIR . +// END_FILE_DIR + +// BEGIN_PREFERENCES +#define DEBUG +// END_PREFERENCES + +// BEGIN_INCLUDE +#include "debug_test_host.dm" +// END_INCLUDE diff --git a/tests/test_runner/src/main.rs b/tests/test_runner/src/main.rs index 813bd232..c3fb2839 100644 --- a/tests/test_runner/src/main.rs +++ b/tests/test_runner/src/main.rs @@ -2,48 +2,14 @@ mod paths; use std::process::Command; -trait ByondCommand { - fn with_byond_paths(&mut self) -> &mut Self; -} - -#[cfg(unix)] -impl ByondCommand for Command { - // TODO: This doesn't read very nice - fn with_byond_paths(&mut self) -> &mut Command { - let byond_system = paths::find_byond(); - let byond_bin = paths::find_byond_bin(); - - let path = format!( - "{}:{}", - byond_bin.as_os_str().to_str().unwrap(), - std::env::var_os("PATH").unwrap().to_str().unwrap() - ); - - let ld_library_path = format!( - "{}:{}", - byond_bin.as_os_str().to_str().unwrap(), - std::env::var_os("LD_LIBRARY_PATH").unwrap().to_str().unwrap() - ); - - self.env("BYOND_SYSTEM", byond_system) - .env("PATH", path) - .env("LD_LIBRARY_PATH", ld_library_path) - } -} - -#[cfg(windows)] -impl ByondCommand for Command { - fn with_byond_paths(&mut self) -> &mut Command { - self - } -} +use paths::ByondCommand; fn main() { let res = Command::new(paths::find_dm()).with_byond_paths().arg(paths::find_dme()).status().unwrap(); assert!(res.success(), "dreamdaemon build failed"); - // Here we depend on BYOND not fucking with stderr too much so we can hijack it - // for our own communication + // Here we depend on BYOND not fucking with stderr too much so we can hijack + // it for our own communication let output = Command::new(paths::find_dreamdaemon()) .with_byond_paths() .env("AUXTEST_DLL", paths::find_dll()) diff --git a/tests/test_runner/src/paths.rs b/tests/test_runner/src/paths.rs index 00179f29..0cf3d49c 100644 --- a/tests/test_runner/src/paths.rs +++ b/tests/test_runner/src/paths.rs @@ -1,4 +1,40 @@ -use std::path::PathBuf; +use std::{path::PathBuf, process::Command}; + +pub trait ByondCommand { + fn with_byond_paths(&mut self) -> &mut Self; +} + +#[cfg(unix)] +impl ByondCommand for Command { + // TODO: This doesn't read very nice + fn with_byond_paths(&mut self) -> &mut Command { + let byond_system = find_byond(); + let byond_bin = find_byond_bin(); + + let path = format!( + "{}:{}", + byond_bin.as_os_str().to_str().unwrap(), + std::env::var_os("PATH").unwrap().to_str().unwrap() + ); + + let ld_library_path = format!( + "{}:{}", + byond_bin.as_os_str().to_str().unwrap(), + std::env::var_os("LD_LIBRARY_PATH").unwrap().to_str().unwrap() + ); + + self.env("BYOND_SYSTEM", byond_system) + .env("PATH", path) + .env("LD_LIBRARY_PATH", ld_library_path) + } +} + +#[cfg(windows)] +impl ByondCommand for Command { + fn with_byond_paths(&mut self) -> &mut Command { + self + } +} pub fn find_byond() -> PathBuf { let path = PathBuf::from(std::env::var_os("BYOND_PATH").unwrap()); @@ -34,7 +70,7 @@ pub fn find_dreamdaemon() -> PathBuf { path.push("bin/DreamDaemon"); #[cfg(windows)] - path.push("bin/dreamdaemon.exe"); + path.push("bin/dd.exe"); assert!(path.is_file(), "couldn't find dreamdaemon"); path