mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a23ede433b | |||
| 346b0a139d | |||
| f5cda2901f | |||
| 2dda5c8706 | |||
| d6aa2ddce3 | |||
| 6f9801a185 | |||
| c82edae338 | |||
| 57327c1f73 | |||
| e3a22bf7d0 | |||
| 3718d914fd | |||
| 97a8b63c9c | |||
| d6d0b66078 | |||
| b173598320 | |||
| f211eede3b | |||
| b123ed9fa3 | |||
| 2b0d8e5423 | |||
| 49b8024fbe | |||
| be987e1434 | |||
| 27e2b89fcf | |||
| c261a746a3 | |||
| c290e60af1 | |||
| 19142a30c6 | |||
| 2ca317d742 | |||
| 45963aeea5 | |||
| 4929f2cc51 | |||
| 90e51f5f09 | |||
| 9b7aeccd48 | |||
| 10d55bd25c | |||
| 16224cd5a6 | |||
| d62d4c852a | |||
| 6d6349c79d | |||
| 9d2eec1732 | |||
| d6da1656b8 | |||
| ae7bac65d5 | |||
| 99c4d6dff8 | |||
| 74600b4b25 | |||
| 6cae2ed3ac | |||
| 611b56186d | |||
| dfa673e6e9 | |||
| be74e3358a | |||
| 4cc85d595f | |||
| b2d7a12033 | |||
| 65cffbba0c | |||
| 5f8e3b9760 | |||
| 397a15349c | |||
| 235a4af0a9 | |||
| b171d48f87 | |||
| bfeba94e20 | |||
| aa0bde0999 | |||
| 72e4a6b94a | |||
| 88f00f5638 | |||
| db21800d9f | |||
| 4a63f469bf |
Generated
+73
-224
@@ -370,9 +370,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
|
||||
|
||||
[[package]]
|
||||
name = "as-raw-xcb-connection"
|
||||
@@ -728,9 +728,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "auto_enums"
|
||||
version = "0.8.8"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03"
|
||||
checksum = "2e4487600931c9a89f8db7ffbdf3fbdd45bb7bd85e26861f659a463cd0dff966"
|
||||
dependencies = [
|
||||
"derive_utils",
|
||||
"proc-macro2",
|
||||
@@ -922,9 +922,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
"zeroize",
|
||||
@@ -981,9 +981,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a"
|
||||
checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
@@ -1064,9 +1064,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
@@ -1148,9 +1148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.63"
|
||||
version = "1.2.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -1781,9 +1781,6 @@ name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
@@ -1837,7 +1834,7 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"block-buffer 0.12.1",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.2",
|
||||
"ctutils",
|
||||
@@ -2594,24 +2591,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ggrs"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/gschup/ggrs.git#1f14703a5859c2bcb0fa789beab15e80b572775f"
|
||||
source = "git+https://github.com/gschup/ggrs.git#6cbcbbfa0bf799a7bf4d8a1f0401ba7a15ae6f4f"
|
||||
dependencies = [
|
||||
"bincode 1.3.3",
|
||||
"bitfield-rle",
|
||||
@@ -2776,7 +2771,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gopher64"
|
||||
version = "1.1.24"
|
||||
version = "1.1.30"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -2870,9 +2865,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.14"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
|
||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -3590,12 +3585,6 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a2c462a4d927d512f5f882a033ddd62f33a05bb9f230d98f736ac3dc85938f"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
@@ -3671,8 +3660,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3916,9 +3903,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.100"
|
||||
version = "0.3.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
|
||||
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -3980,12 +3967,6 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
@@ -4123,9 +4104,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
@@ -4264,9 +4245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
@@ -5090,9 +5071,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.80"
|
||||
version = "0.10.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
||||
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
@@ -5130,9 +5111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.116"
|
||||
version = "0.9.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
||||
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -5742,7 +5723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
dependencies = [
|
||||
"chacha20 0.10.0",
|
||||
"getrandom 0.4.2",
|
||||
"getrandom 0.4.3",
|
||||
"rand_core 0.10.1",
|
||||
"serde",
|
||||
]
|
||||
@@ -6651,7 +6632,7 @@ dependencies = [
|
||||
"bzip2",
|
||||
"cbc 0.2.1",
|
||||
"crc32fast",
|
||||
"getrandom 0.4.2",
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
"lzma-rust2",
|
||||
"ppmd-rust",
|
||||
@@ -6894,9 +6875,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
@@ -7192,9 +7173,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1804632b66a35ca2b1d277eb0a138e10f46cb365b9a6d297e876b69ef79de43"
|
||||
checksum = "0811b01ca2c4e8718760713911feaf4675c24f94e50530a015ec646cfb622f7c"
|
||||
dependencies = [
|
||||
"skrifa 0.42.1",
|
||||
"yazi",
|
||||
@@ -7203,9 +7184,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
version = "2.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7295,7 +7276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.2",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -7372,12 +7353,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
version = "0.3.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"js-sys",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
@@ -7388,15 +7368,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
version = "0.2.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -8003,7 +7983,7 @@ version = "1.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
@@ -8103,27 +8083,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.123"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
|
||||
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -8134,9 +8105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.73"
|
||||
version = "0.4.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf"
|
||||
checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -8144,9 +8115,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.123"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
|
||||
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -8154,9 +8125,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.123"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
|
||||
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -8167,47 +8138,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.123"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
|
||||
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.15"
|
||||
@@ -8258,9 +8195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.12"
|
||||
version = "0.32.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||
checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"wayland-backend",
|
||||
@@ -8345,9 +8282,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.100"
|
||||
version = "0.3.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69"
|
||||
checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -9338,100 +9275,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write-fonts"
|
||||
version = "0.45.0"
|
||||
@@ -9760,18 +9609,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.50"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9801,18 +9650,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9866,7 +9715,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.4.2",
|
||||
"getrandom 0.4.3",
|
||||
"hmac 0.13.0",
|
||||
"indexmap",
|
||||
"lzma-rust2",
|
||||
@@ -9883,9 +9732,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"
|
||||
checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "1.1.24"
|
||||
version = "1.1.30"
|
||||
edition = "2024"
|
||||
rust-version = "1.96.0"
|
||||
|
||||
@@ -19,12 +19,12 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-big-array = "0.5"
|
||||
sha2 = "0.11"
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs", "process"] }
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "fs", "process"] }
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tokio-tungstenite = { version = "0.29", features = ["native-tls"] }
|
||||
tokio-tungstenite = { version = "0.29", features = ["native-tls-vendored"] }
|
||||
matchbox_socket = "0.14"
|
||||
ggrs = { git = "https://github.com/gschup/ggrs.git", features = ["sync-send"] }
|
||||
rustc-hash = "2.1"
|
||||
@@ -61,7 +61,6 @@ slint-build = { version = "1.12", optional = true }
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
strip = true
|
||||
|
||||
@@ -69,7 +68,6 @@ strip = true
|
||||
inherits = "release"
|
||||
debug = true
|
||||
strip = false
|
||||
panic = "unwind"
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "Gopher64"
|
||||
|
||||
@@ -41,7 +41,9 @@ Keys are mapped according to [these defaults](https://github.com/gopher64/gopher
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the [server](https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
Gopher64 supports P2P netplay (online play with others).
|
||||
|
||||
Please read the [guide](https://github.com/gopher64/gopher64/wiki/Netplay-Guide) before trying out netplay.
|
||||
|
||||
## portable mode
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Spenden (über GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokales Spiel"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Controller-Konfiguration"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Über"
|
||||
@@ -240,7 +240,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Sitzung erstellen"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
@@ -345,67 +345,83 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Feedback geben"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Warteraum"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sitzungsname:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Spielname:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Spieler 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Spieler 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Spieler 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Spieler 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Chatnachricht eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Nachricht senden"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Eine zu niedrige Eingabeverzögerung führt zu Ruckeln im Spiel.\n"
|
||||
"Sie können die Eingabeverzögerung im Spiel mit Alt+[ verringern und mit "
|
||||
"Alt+] erhöhen."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Lokale Eingabeverzögerung:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Empfohlen: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Spiel starten"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Warten auf P2P-Mesh "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: Gemini\n"
|
||||
"Language-Team: Spanish (Spain)\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Donar (a través de GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Juego local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Config. del mando"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Trucos"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Acerca de"
|
||||
@@ -240,7 +240,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Crear sesión"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
@@ -345,67 +345,84 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Enviar comentarios"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Sala de espera"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nombre de la sesión:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nombre del juego:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Jugador 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Jugador 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Jugador 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Jugador 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Escribe un mensaje en el chat"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Enviar mensaje"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Configurar un retardo de entrada demasiado bajo hará que el juego se "
|
||||
"ralentice.\n"
|
||||
"Puedes reducir el retardo de entrada en el juego pulsando Alt+[ y "
|
||||
"aumentarlo con Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Retardo de entrada local:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recomendado: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Iniciar juego"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Esperando malla P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French (France)\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Faire un don (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Jeu local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Configuration manette"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Triche"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "À propos"
|
||||
@@ -240,7 +240,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Créer la session"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
@@ -346,67 +346,83 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Donner votre avis"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay : salle d'attente"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nom de la session :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nom du jeu :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Joueur 1 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Joueur 2 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Joueur 3 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Joueur 4 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Saisir un message"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Envoyer"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Régler le délai d'entrée trop bas provoquera des ralentissements.\n"
|
||||
"Vous pouvez diminuer le délai d'entrée en jeu avec Alt+[ et l'augmenter "
|
||||
"avec Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Délai d'entrée local :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recommandé : "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Lancer la partie"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "En attente du maillage P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
@@ -238,7 +238,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
@@ -343,67 +343,79 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\nYou can lower "
|
||||
"the input delay in-game by pressing Alt+[ and raise it with Alt+]."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "GitHubで寄付"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "ローカルゲーム"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "コントローラー設定"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "ネットプレイ"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "チート"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "バージョン情報"
|
||||
@@ -238,7 +238,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "セッションを作成"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
@@ -343,67 +343,82 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "フィードバックを送る"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "ネットプレイ:待機室"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "セッション名:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "ゲーム名:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "プレイヤー1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "プレイヤー2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "プレイヤー3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "プレイヤー4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "チャットメッセージを入力"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "送信"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"入力遅延を低く設定しすぎるとゲームが遅延します。\n"
|
||||
"Alt+[ でゲーム内の入力遅延を下げ、Alt+] で上げることができます。"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "ローカル入力遅延:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "推奨: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "ゲーム開始"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "P2Pメッシュ接続待機中 "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch (Netherlands)\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Doneren (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokaal spel"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Controllerconfiguratie"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Instellingen"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Over"
|
||||
@@ -240,7 +240,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Sessie aanmaken"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
@@ -345,67 +345,83 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Feedback geven"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: wachtruimte"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sessienaam:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Spelnaam:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Speler 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Speler 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Speler 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Speler 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Chatbericht invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Bericht verzenden"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Een te lage invoervertraging zorgt ervoor dat het spel gaat haperen.\n"
|
||||
"Je kunt de invoervertraging in het spel verlagen met Alt+[ en verhogen met "
|
||||
"Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Lokale invoervertraging:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Aanbevolen: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Spel starten"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Wachten op P2P-mesh "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 18:11-3000\n"
|
||||
"Last-Translator: EDUARDO CARVALHO <eduardotcarvalho@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Doações (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Jogo Local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Configurar Controle"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Trapaças"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Configurações"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Sobre"
|
||||
@@ -240,7 +240,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Criar Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
@@ -346,67 +346,83 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Fornecer Feedback"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Aguardando Sala"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nome da Sessão:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nome do Jogo:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Jogador 1"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Jogador 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Jogador 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Jogador 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Escrever mensagem no chat"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Enviar Mensagem"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Definir o atraso de entrada muito baixo causará lag no jogo.\n"
|
||||
"Você pode diminuir o atraso de entrada no jogo pressionando Alt+[ e "
|
||||
"aumentá-lo com Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Atraso de entrada local:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recomendado: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Iniciar jogo"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Aguardando malha P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-05 12:03+0000\n"
|
||||
"POT-Creation-Date: 2026-06-16 12:26+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
@@ -47,37 +47,37 @@ msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Пожертвовать (через GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:147
|
||||
#: src/ui/gui/appwindow.slint:148
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Локальная игра"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:151
|
||||
#: src/ui/gui/appwindow.slint:152
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Настройка контроллера"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:155
|
||||
#: src/ui/gui/appwindow.slint:156
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Сетевая игра"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:159
|
||||
#: src/ui/gui/appwindow.slint:160
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Читы"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:163
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:167
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:171
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "О программе"
|
||||
@@ -239,7 +239,7 @@ msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Создать сессию"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:105
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
@@ -344,67 +344,83 @@ msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Оставить отзыв"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:16
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Сетевая игра: Комната ожидания"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:28
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Имя сессии:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:34
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Название игры:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:44
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Игрок 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:48
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Игрок 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:54
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Игрок 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:58
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Игрок 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:72
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Введите сообщение чата"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:83
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Отправить сообщение"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:100
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Слишком низкая задержка ввода вызовет лаги в игре.\n"
|
||||
"Вы можете уменьшить задержку ввода в игре, нажав Alt+[, и увеличить её с "
|
||||
"помощью Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Локальная задержка ввода:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:115
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Рекомендуется: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:123
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Начать игру"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:131
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Ожидание P2P-сети "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
+25
-26
@@ -622,10 +622,10 @@ static void render_frame(Vulkan::Device &device) {
|
||||
cmd->set_depth_test(false, false);
|
||||
cmd->set_cull_mode(VK_CULL_MODE_NONE);
|
||||
|
||||
VkViewport vp = cmd->get_viewport();
|
||||
// If we don't have an image, we just get a cleared screen in the render
|
||||
// pass.
|
||||
if (image) {
|
||||
VkViewport vp = cmd->get_viewport();
|
||||
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height,
|
||||
image->get_height() / gfx_info.upscale);
|
||||
|
||||
@@ -646,33 +646,32 @@ static void render_frame(Vulkan::Device &device) {
|
||||
// The vertices are constants in the shader.
|
||||
// Draws fullscreen quad using oversized triangle.
|
||||
cmd->draw(3);
|
||||
|
||||
if (!messages.empty()) {
|
||||
Message *message = &messages.front();
|
||||
if (!message->image) {
|
||||
message->image = create_message_image(device, vp.width, message_font,
|
||||
message->message.c_str());
|
||||
SDL_AddTimer(message->milliseconds, pop_message_callback, NULL);
|
||||
}
|
||||
cmd->set_texture(0, 0, message->image->get_view(),
|
||||
Vulkan::StockSampler::NearestClamp);
|
||||
vp.x = floor(vp.x + (vp.width - message->image->get_width()) / 2);
|
||||
vp.y = vp.y + vp.height - message->image->get_height();
|
||||
vp.height = message->image->get_height();
|
||||
vp.width = message->image->get_width();
|
||||
cmd->set_viewport(vp);
|
||||
|
||||
cmd->draw(3);
|
||||
} else if (achievement_progress_indicator_image) {
|
||||
draw_indicator(cmd, achievement_progress_indicator_image, vp);
|
||||
} else if (achievement_challenge_indicator_image &&
|
||||
display_challenge_indicator) {
|
||||
draw_indicator(cmd, achievement_challenge_indicator_image, vp);
|
||||
}
|
||||
if (!messages.empty()) {
|
||||
Message *message = &messages.front();
|
||||
if (!message->image) {
|
||||
message->image = create_message_image(device, vp.width, message_font,
|
||||
message->message.c_str());
|
||||
SDL_AddTimer(message->milliseconds, pop_message_callback, NULL);
|
||||
}
|
||||
cmd->set_texture(0, 0, message->image->get_view(),
|
||||
Vulkan::StockSampler::NearestClamp);
|
||||
vp.x = floor(vp.x + (vp.width - message->image->get_width()) / 2);
|
||||
vp.y = vp.y + vp.height - message->image->get_height();
|
||||
vp.height = message->image->get_height();
|
||||
vp.width = message->image->get_width();
|
||||
cmd->set_viewport(vp);
|
||||
|
||||
if (messages.empty() && display_fps && fps_image) {
|
||||
draw_fps(cmd, vp);
|
||||
}
|
||||
cmd->draw(3);
|
||||
} else if (achievement_progress_indicator_image) {
|
||||
draw_indicator(cmd, achievement_progress_indicator_image, vp);
|
||||
} else if (achievement_challenge_indicator_image &&
|
||||
display_challenge_indicator) {
|
||||
draw_indicator(cmd, achievement_challenge_indicator_image, vp);
|
||||
}
|
||||
|
||||
if (messages.empty() && display_fps && fps_image) {
|
||||
draw_fps(cmd, vp);
|
||||
}
|
||||
|
||||
cmd->end_render_pass();
|
||||
|
||||
+37
-5
@@ -52,6 +52,7 @@ pub fn run_game(
|
||||
rom_contents: &[u8],
|
||||
game_settings: ui::GameSettings,
|
||||
ra_config: retroachievements::RAConfig,
|
||||
netplay_config: Option<netplay::NetplayConfig>,
|
||||
) {
|
||||
device.cpu.overclock = game_settings.overclock;
|
||||
if game_settings.disable_expansion_pak {
|
||||
@@ -62,17 +63,32 @@ pub fn run_game(
|
||||
device.savestate.load_state = true;
|
||||
}
|
||||
|
||||
init_rng(device);
|
||||
|
||||
cart::rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
|
||||
// rdram pointer is shared with parallel-rdp and retroachievements
|
||||
rdram::init(device);
|
||||
|
||||
ui::video::init(device);
|
||||
ui::video::init(device, netplay_config.is_some());
|
||||
ui::audio::init(device);
|
||||
ui::input::init(&mut device.ui);
|
||||
|
||||
// must be after video init
|
||||
if let Some(netplay_config) = &netplay_config {
|
||||
device.netplay = netplay::init(
|
||||
device,
|
||||
netplay_config,
|
||||
cart::rom::is_system_pal(rom_contents),
|
||||
);
|
||||
if device.netplay.is_none() {
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
init_rng_rtc(device);
|
||||
|
||||
// must be after video init
|
||||
let (discord_watch_tx, discord_handle) = if ra_config.enabled && device.netplay.is_none() {
|
||||
retroachievements::load_game(rom_contents, rom_contents.len(), ra_config.rich_presence)
|
||||
@@ -105,6 +121,11 @@ pub fn run_game(
|
||||
retroachievements::unload_game(discord_watch_tx, discord_handle);
|
||||
|
||||
ui::storage::write_saves(device);
|
||||
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
netplay::close(netplay);
|
||||
}
|
||||
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
@@ -114,7 +135,7 @@ fn set_rng() -> rand::rngs::Xoshiro256PlusPlus {
|
||||
rand::rngs::Xoshiro256PlusPlus::try_from_rng(&mut rand::rngs::SysRng).unwrap()
|
||||
}
|
||||
|
||||
fn init_rng(device: &mut Device) {
|
||||
fn init_rng_rtc(device: &mut Device) {
|
||||
let mut rng_seed = set_rng().next_u64();
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number == 0 {
|
||||
@@ -124,6 +145,18 @@ fn init_rng(device: &mut Device) {
|
||||
}
|
||||
}
|
||||
device.rng = rand::rngs::Xoshiro256PlusPlus::seed_from_u64(rng_seed);
|
||||
|
||||
device.cart.rtc_timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
|
||||
} else {
|
||||
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
|
||||
@@ -314,7 +347,6 @@ impl Device {
|
||||
savestate: savestates::Savestate {
|
||||
save_state: false,
|
||||
load_state: false,
|
||||
save_rewind: false,
|
||||
load_rewind: false,
|
||||
last_rewind_saved: 0.0,
|
||||
rewind_pool: std::sync::Arc::new(std::sync::Mutex::new(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
@@ -122,18 +121,6 @@ pub fn dma_write(
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: &[u8]) {
|
||||
device.cart.rtc_timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
|
||||
} else {
|
||||
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
|
||||
}
|
||||
}
|
||||
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
|
||||
|
||||
device.cart.rom = rom_file.to_vec();
|
||||
|
||||
+3
-10
@@ -219,16 +219,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
data.data_size & 0xFFFFFF; // length
|
||||
device.cart.sc64.usb_buffer = data.data; // store the data to be read
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
|
||||
panic!("cart_rx lagged: {err}");
|
||||
}
|
||||
_ => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] = 0; // length
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] = 0; // length
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,8 @@ pub const EVENT_TYPE_VRU: usize = 10;
|
||||
pub const EVENT_TYPE_PAK: usize = 11;
|
||||
pub const EVENT_TYPE_SAVE: usize = 12;
|
||||
pub const EVENT_TYPE_NMI: usize = 13;
|
||||
pub const EVENT_TYPE_COUNT: usize = 14;
|
||||
pub const EVENT_TYPE_UPDATE_SCREEN: usize = 14;
|
||||
pub const EVENT_TYPE_COUNT: usize = 15;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Event {
|
||||
@@ -65,6 +66,7 @@ fn get_event_handler(name: usize) -> fn(&mut device::Device) {
|
||||
EVENT_TYPE_PAK => device::controller::pak_switch_event,
|
||||
EVENT_TYPE_SAVE => ui::storage::save_event,
|
||||
EVENT_TYPE_NMI => device::exceptions::reset_event,
|
||||
EVENT_TYPE_UPDATE_SCREEN => device::vi::update_screen,
|
||||
_ => dummy_event,
|
||||
}
|
||||
}
|
||||
|
||||
+42
-32
@@ -3,14 +3,14 @@ use crate::{cheats, device, netplay, retroachievements, savestates, ui};
|
||||
const VI_STATUS_REG: usize = 0;
|
||||
const VI_ORIGIN_REG: usize = 1;
|
||||
//const VI_WIDTH_REG: usize = 2;
|
||||
//const VI_V_INTR_REG: usize = 3;
|
||||
const VI_V_INTR_REG: usize = 3;
|
||||
const VI_CURRENT_REG: usize = 4;
|
||||
//const VI_BURST_REG: usize = 5;
|
||||
const VI_V_SYNC_REG: usize = 6;
|
||||
const VI_H_SYNC_REG: usize = 7;
|
||||
//const VI_LEAP_REG: usize = 8;
|
||||
//const VI_H_START_REG: usize = 9;
|
||||
//const VI_V_START_REG: usize = 10;
|
||||
const VI_V_START_REG: usize = 10;
|
||||
//const VI_V_BURST_REG: usize = 11;
|
||||
//const VI_X_SCALE_REG: usize = 12;
|
||||
//const VI_Y_SCALE_REG: usize = 13;
|
||||
@@ -103,7 +103,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
if device.netplay.is_none() {
|
||||
savestates::process_savestates(device);
|
||||
}
|
||||
let _ = device.ui.video.fps_tx.as_ref().unwrap().try_send(true);
|
||||
if !netplay::in_rollback(device.netplay.as_ref()) {
|
||||
let _ = device.ui.video.fps_tx.as_ref().unwrap().send(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -113,50 +115,37 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
|
||||
pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
device.vi.elapsed_time += device.vi.frame_time;
|
||||
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
pub fn update_screen(device: &mut device::Device) {
|
||||
if !netplay::in_rollback(device.netplay.as_ref()) {
|
||||
ui::video::render_frame();
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
}
|
||||
|
||||
ui::video::render_frame();
|
||||
let _ = device.ui.video.vis_tx.as_ref().unwrap().try_send(true);
|
||||
|
||||
retroachievements::do_frame();
|
||||
|
||||
let (speed_limiter_toggled, paused) = ui::video::check_callback(device);
|
||||
|
||||
if device.netplay.is_none()
|
||||
&& device.ui.config.emulation.rewind
|
||||
&& device.vi.elapsed_time - device.savestate.last_rewind_saved > 1.0
|
||||
{
|
||||
device.savestate.save_rewind = true;
|
||||
device.savestate.last_rewind_saved = device.vi.elapsed_time;
|
||||
}
|
||||
|
||||
if speed_limiter_toggled {
|
||||
reset_pace_deadline(device);
|
||||
}
|
||||
|
||||
if device
|
||||
.speed_limiter
|
||||
.frame_counter
|
||||
.is_multiple_of(device.speed_limiter.limit_freq)
|
||||
if !netplay::in_rollback(device.netplay.as_ref())
|
||||
&& device
|
||||
.speed_limiter
|
||||
.frame_counter
|
||||
.is_multiple_of(device.speed_limiter.limit_freq)
|
||||
&& device.speed_limiter.enabled
|
||||
{
|
||||
speed_limiter(device);
|
||||
}
|
||||
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
ui::video::update_screen();
|
||||
device.speed_limiter.frame_counter += 1;
|
||||
if !netplay::in_rollback(device.netplay.as_ref()) {
|
||||
ui::video::update_screen();
|
||||
device.speed_limiter.frame_counter += 1;
|
||||
let _ = device.ui.video.vis_tx.as_ref().unwrap().send(true);
|
||||
}
|
||||
|
||||
if device.netplay.is_some() {
|
||||
device.netplay.as_mut().unwrap().inputs = netplay::process_requests(device);
|
||||
}
|
||||
|
||||
if device.netplay.is_none() && paused {
|
||||
} else if paused {
|
||||
if retroachievements::get_hardcore() {
|
||||
ui::video::onscreen_message(
|
||||
"Cannot pause in RA hardcore mode",
|
||||
@@ -166,6 +155,16 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
ui::video::pause_loop(&mut device.ui, device.vi.frame_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
device.vi.elapsed_time += device.vi.frame_time;
|
||||
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
|
||||
retroachievements::do_frame();
|
||||
|
||||
/* toggle vi field if in interlaced mode */
|
||||
device.vi.field ^= (device.vi.regs[VI_STATUS_REG] >> 6) & 0x1;
|
||||
@@ -176,7 +175,17 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
device,
|
||||
device::events::EVENT_TYPE_VI,
|
||||
device.cpu.next_event_count + device.vi.delay,
|
||||
)
|
||||
);
|
||||
|
||||
let v_intr = device.vi.regs[VI_V_INTR_REG] & 0x3FF;
|
||||
let v_start = (device.vi.regs[VI_V_START_REG] >> 16) & 0x3FF;
|
||||
if v_start > v_intr {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_UPDATE_SCREEN,
|
||||
(v_start - v_intr) as u64 * device.vi.count_per_scanline,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
@@ -212,6 +221,7 @@ fn speed_limiter(device: &mut device::Device) {
|
||||
if now < deadline {
|
||||
let dur = deadline - now;
|
||||
spin_sleep::sleep(dur);
|
||||
|
||||
if dur < device.speed_limiter.min_wait_time {
|
||||
device.speed_limiter.min_wait_time = dur;
|
||||
}
|
||||
|
||||
+42
-24
@@ -104,6 +104,19 @@ fn set_app_id() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_runtime() -> (tokio::sync::oneshot::Sender<()>, tokio::runtime::Handle) {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (close_tx, close_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().expect("Failed to build runtime");
|
||||
tx.send(rt.handle().clone()).unwrap();
|
||||
rt.block_on(close_rx).unwrap();
|
||||
});
|
||||
|
||||
(close_tx, rx.recv().unwrap())
|
||||
}
|
||||
|
||||
pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
let dirs = ui::get_dirs();
|
||||
|
||||
@@ -169,22 +182,7 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
let mut shutdown_tx = None;
|
||||
let mut usb_handle = None;
|
||||
|
||||
if let Some(server_addr) = args.netplay_server_addr
|
||||
&& let Some(player_number) = args.netplay_player_number
|
||||
&& let Some(number_of_players) = args.netplay_number_of_players
|
||||
&& let Some(input_delay) = args.netplay_input_delay
|
||||
{
|
||||
device.netplay = netplay::init(
|
||||
server_addr,
|
||||
player_number,
|
||||
number_of_players,
|
||||
input_delay,
|
||||
device::cart::rom::is_system_pal(&rom_contents),
|
||||
);
|
||||
if device.netplay.is_none() {
|
||||
return Err(Error::other("Failed to connect to netplay server"));
|
||||
}
|
||||
} else {
|
||||
if args.netplay_server_addr.is_none() {
|
||||
for i in 0..4 {
|
||||
if device.ui.config.input.transfer_pak[i]
|
||||
&& !device.ui.config.input.gb_rom_path[i].is_empty()
|
||||
@@ -210,7 +208,7 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
{
|
||||
let username = args.ra_username.unwrap_or(ra_config.username.clone());
|
||||
retroachievements::init_client(
|
||||
if cfg!(ra_hardcore_enabled) && device.netplay.is_none() {
|
||||
if cfg!(ra_hardcore_enabled) && args.netplay_server_addr.is_none() {
|
||||
ra_config.hardcore
|
||||
} else {
|
||||
false
|
||||
@@ -228,16 +226,29 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
tx.send(false).unwrap();
|
||||
}
|
||||
|
||||
let join_handle = std::thread::spawn(move || {
|
||||
rx.blocking_recv().unwrap();
|
||||
});
|
||||
join_handle.join().unwrap();
|
||||
rx.blocking_recv().unwrap();
|
||||
|
||||
ra_config
|
||||
} else {
|
||||
retroachievements::RAConfig::default()
|
||||
};
|
||||
|
||||
let netplay_config = if let Some(server_addr) = args.netplay_server_addr
|
||||
&& let Some(player_number) = args.netplay_player_number
|
||||
&& let Some(number_of_players) = args.netplay_number_of_players
|
||||
&& let Some(input_delay) = args.netplay_input_delay
|
||||
{
|
||||
Some(netplay::NetplayConfig {
|
||||
server_addr,
|
||||
player_number,
|
||||
number_of_players,
|
||||
input_delay,
|
||||
ice_config_path: dirs.cache_dir.join("ice_config.json"),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
device::run_game(
|
||||
&mut device,
|
||||
&rom_contents,
|
||||
@@ -248,6 +259,7 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
load_savestate_slot: args.load_state,
|
||||
},
|
||||
ra_config,
|
||||
netplay_config,
|
||||
);
|
||||
|
||||
// on Android, the client is shut down in the app_window function
|
||||
@@ -333,8 +345,10 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[unsafe(no_mangle)]
|
||||
#[tokio::main]
|
||||
async fn android_main(app: slint::android::AndroidApp) {
|
||||
fn android_main(app: slint::android::AndroidApp) {
|
||||
let (close_tx, handle) = create_runtime();
|
||||
let _guard = handle.enter();
|
||||
|
||||
slint::android::init_with_event_listener(app.clone(), move |event| match event {
|
||||
slint::android::android_activity::PollEvent::Main(main_event) => match main_event {
|
||||
slint::android::android_activity::MainEvent::TerminateWindow { .. } => {
|
||||
@@ -342,7 +356,10 @@ async fn android_main(app: slint::android::AndroidApp) {
|
||||
&& let Some(weak_app_window) = weak_app_window.as_ref()
|
||||
{
|
||||
weak_app_window
|
||||
.upgrade_in_event_loop(move |handle| ui::gui::save_settings(&handle))
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
ui::gui::save_settings(&handle);
|
||||
handle.invoke_netplay_close();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -364,6 +381,7 @@ async fn android_main(app: slint::android::AndroidApp) {
|
||||
std::fs::create_dir_all(dirs.data_dir.join("states")).unwrap();
|
||||
|
||||
ui::gui::app_window(&app_window, true);
|
||||
close_tx.send(()).unwrap();
|
||||
*android::WEAK_SLINT_WINDOW.lock().unwrap() = None;
|
||||
*android::ANDROID_APP.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
+7
-3
@@ -5,8 +5,12 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
fn main() -> std::io::Result<()> {
|
||||
let (close_tx, handle) = gopher64::create_runtime();
|
||||
let _guard = handle.enter();
|
||||
|
||||
let args = gopher64::Args::parse();
|
||||
gopher64::run(args, std::env::args().count())
|
||||
let result = gopher64::run(args, std::env::args().count());
|
||||
close_tx.send(()).unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
+232
-69
@@ -1,8 +1,14 @@
|
||||
use crate::device;
|
||||
use crate::savestates;
|
||||
use crate::ui;
|
||||
use sha2::digest::Digest;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RtcIceServerConfig {
|
||||
pub urls: Vec<String>,
|
||||
pub username: Option<String>,
|
||||
pub credential: Option<String>,
|
||||
}
|
||||
|
||||
pub struct GgrsConfig;
|
||||
impl ggrs::Config for GgrsConfig {
|
||||
type Input = ui::input::InputData;
|
||||
@@ -11,21 +17,18 @@ impl ggrs::Config for GgrsConfig {
|
||||
type Address = matchbox_socket::PeerId;
|
||||
}
|
||||
|
||||
pub struct MatchboxSocket(matchbox_socket::WebRtcSocket);
|
||||
pub struct MatchboxChannel {
|
||||
channel: matchbox_socket::WebRtcChannel,
|
||||
}
|
||||
|
||||
impl ggrs::NonBlockingSocket<matchbox_socket::PeerId> for MatchboxSocket {
|
||||
impl ggrs::NonBlockingSocket<matchbox_socket::PeerId> for MatchboxChannel {
|
||||
fn send_to(&mut self, msg: &ggrs::Message, addr: &matchbox_socket::PeerId) {
|
||||
let encoded = postcard::to_stdvec(msg).expect("serialization failed");
|
||||
let channel = self.0.get_channel_mut(0).unwrap();
|
||||
if channel.config().max_retransmits != Some(0) || channel.config().ordered {
|
||||
eprintln!("Sending GGRS traffic over reliable channel");
|
||||
}
|
||||
channel.send(encoded.into(), *addr);
|
||||
let _ = self.channel.try_send(encoded.into(), *addr);
|
||||
}
|
||||
|
||||
fn receive_all_messages(&mut self) -> Vec<(matchbox_socket::PeerId, ggrs::Message)> {
|
||||
let channel = self.0.get_channel_mut(0).unwrap();
|
||||
channel
|
||||
self.channel
|
||||
.receive()
|
||||
.iter()
|
||||
.filter_map(|(peer, packet)| {
|
||||
@@ -36,7 +39,16 @@ impl ggrs::NonBlockingSocket<matchbox_socket::PeerId> for MatchboxSocket {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetplayConfig {
|
||||
pub server_addr: String,
|
||||
pub player_number: usize,
|
||||
pub number_of_players: usize,
|
||||
pub input_delay: usize,
|
||||
pub ice_config_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
pub struct Netplay {
|
||||
pub disconnected: bool,
|
||||
pub session: ggrs::P2PSession<GgrsConfig>,
|
||||
pub reliable_channel: matchbox_socket::WebRtcChannel,
|
||||
pub peers: Vec<matchbox_socket::PeerId>,
|
||||
@@ -48,6 +60,7 @@ pub struct Netplay {
|
||||
pub inputs: Vec<(ui::input::InputData, ggrs::InputStatus)>,
|
||||
pub requests: std::collections::VecDeque<ggrs::GgrsRequest<GgrsConfig>>,
|
||||
pub incoming_message: Vec<u8>,
|
||||
pub ice_config_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -61,7 +74,12 @@ fn send_message(netplay: &mut Netplay, message: NetplayMessage) {
|
||||
let chunks = data.chunks(16384).collect::<Vec<&[u8]>>();
|
||||
for peer in netplay.peers.iter() {
|
||||
for chunk in chunks.iter() {
|
||||
netplay.reliable_channel.send(chunk.to_vec().into(), *peer);
|
||||
if let Err(e) = netplay
|
||||
.reliable_channel
|
||||
.try_send(chunk.to_vec().into(), *peer)
|
||||
{
|
||||
eprintln!("Failed to send message: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +104,8 @@ fn process_reliable_messages(netplay: &mut Netplay) {
|
||||
.messages
|
||||
.insert(decoded_message.name, decoded_message.data);
|
||||
netplay.incoming_message.clear();
|
||||
check_input_delay(netplay);
|
||||
check_disconnect(netplay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,16 +130,18 @@ fn receive_message(netplay: &mut Netplay, name: &str) -> Vec<u8> {
|
||||
|
||||
fn send_player_number(
|
||||
channel: &mut matchbox_socket::WebRtcChannel,
|
||||
peers: Vec<matchbox_socket::PeerId>,
|
||||
peers: &[matchbox_socket::PeerId],
|
||||
player_number: usize,
|
||||
) {
|
||||
let message = NetplayMessage {
|
||||
name: "player_number".to_string(),
|
||||
data: player_number.to_be_bytes().to_vec(),
|
||||
data: (player_number as u64).to_be_bytes().to_vec(),
|
||||
};
|
||||
let data = postcard::to_stdvec(&message).unwrap();
|
||||
for peer in peers {
|
||||
channel.send(data.clone().into(), peer);
|
||||
if let Err(e) = channel.try_send(data.clone().into(), *peer) {
|
||||
eprintln!("Failed to send message: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +153,7 @@ fn get_player_numbers(
|
||||
let message = postcard::from_bytes::<NetplayMessage>(&data).unwrap();
|
||||
if message.name == "player_number" {
|
||||
player_numbers.insert(
|
||||
usize::from_be_bytes(message.data.try_into().unwrap()),
|
||||
u64::from_be_bytes(message.data.try_into().unwrap()) as usize,
|
||||
Some(peer),
|
||||
);
|
||||
}
|
||||
@@ -179,9 +201,13 @@ pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<
|
||||
}
|
||||
|
||||
pub fn send_input_delay(netplay: &mut Netplay, input_delay: usize) {
|
||||
if input_delay < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = NetplayMessage {
|
||||
name: "input_delay".to_string(),
|
||||
data: input_delay.to_be_bytes().to_vec(),
|
||||
data: (input_delay as u64).to_be_bytes().to_vec(),
|
||||
};
|
||||
send_message(netplay, message);
|
||||
change_input_delay(netplay, input_delay);
|
||||
@@ -203,13 +229,39 @@ fn change_input_delay(netplay: &mut Netplay, input_delay: usize) {
|
||||
|
||||
fn check_input_delay(netplay: &mut Netplay) {
|
||||
if let Some(data) = netplay.messages.remove("input_delay") {
|
||||
let input_delay = usize::from_be_bytes(data.try_into().unwrap());
|
||||
let input_delay = u64::from_be_bytes(data.try_into().unwrap()) as usize;
|
||||
if input_delay != netplay.input_delay {
|
||||
change_input_delay(netplay, input_delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_disconnect(netplay: &mut Netplay) {
|
||||
if !netplay.disconnected && netplay.messages.remove("disconnect").is_some() {
|
||||
netplay.disconnected = true;
|
||||
ui::video::onscreen_message(
|
||||
"Player disconnected, session has ended",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn pending_frames(netplay: &Netplay) -> usize {
|
||||
netplay
|
||||
.requests
|
||||
.iter()
|
||||
.filter(|r| matches!(r, ggrs::GgrsRequest::AdvanceFrame { .. }))
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn in_rollback(netplay: Option<&Netplay>) -> bool {
|
||||
if let Some(netplay) = netplay {
|
||||
pending_frames(netplay) != 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_requests(
|
||||
device: &mut device::Device,
|
||||
) -> Vec<(ui::input::InputData, ggrs::InputStatus)> {
|
||||
@@ -217,7 +269,7 @@ pub fn process_requests(
|
||||
if let Some(request) = device.netplay.as_mut().unwrap().requests.pop_front() {
|
||||
match request {
|
||||
ggrs::GgrsRequest::SaveGameState { cell, frame } => {
|
||||
savestates::create_savestate(device, true, Some(frame));
|
||||
//savestates::create_savestate(device, true, Some(frame));
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
for reg in device.cpu.cop0.regs.as_ref() {
|
||||
@@ -226,36 +278,35 @@ pub fn process_requests(
|
||||
let hash = u128::from_be_bytes(hasher.finalize()[..16].try_into().unwrap());
|
||||
cell.save(frame, Some(frame), Some(hash));
|
||||
}
|
||||
ggrs::GgrsRequest::LoadGameState { cell, frame: _ } => {
|
||||
if let Some(frame) = cell.load() {
|
||||
savestates::load_savestate(device, true, Some(frame));
|
||||
}
|
||||
ggrs::GgrsRequest::LoadGameState { cell: _, frame: _ } => {
|
||||
// if let Some(frame) = cell.load() {
|
||||
// savestates::load_savestate(device, true, Some(frame));
|
||||
// }
|
||||
}
|
||||
ggrs::GgrsRequest::AdvanceFrame { inputs } => {
|
||||
return inputs;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the screen doesn't freeze
|
||||
process_netplay(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_netplay(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
|
||||
fn poll_clients(netplay: &mut Netplay) {
|
||||
netplay.session.poll_remote_clients();
|
||||
for event in netplay.session.events() {
|
||||
match event {
|
||||
ggrs::GgrsEvent::Synchronizing { .. } => {}
|
||||
ggrs::GgrsEvent::Synchronized { .. } => {}
|
||||
ggrs::GgrsEvent::Disconnected { .. } => {
|
||||
eprintln!("peer disconnected");
|
||||
ui::video::onscreen_message(
|
||||
"Peer disconnected",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_LONG,
|
||||
);
|
||||
if !netplay.disconnected {
|
||||
netplay.disconnected = true;
|
||||
ui::video::onscreen_message(
|
||||
"Lost connection to peer(s)",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
ggrs::GgrsEvent::NetworkInterrupted { .. } => {
|
||||
println!("network interrupted");
|
||||
@@ -275,9 +326,13 @@ fn process_netplay(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_netplay(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
|
||||
poll_clients(netplay);
|
||||
process_reliable_messages(netplay);
|
||||
check_input_delay(netplay);
|
||||
advance_frame(device);
|
||||
}
|
||||
|
||||
@@ -289,6 +344,28 @@ fn advance_frame(device: &mut device::Device) {
|
||||
.session
|
||||
.add_local_input(local_handle, local_input)
|
||||
.unwrap();
|
||||
|
||||
// avoid rollback
|
||||
while !netplay.disconnected
|
||||
&& netplay.session.current_frame() > netplay.session.confirmed_frame()
|
||||
&& netplay.session.confirmed_frame() != ggrs::NULL_FRAME
|
||||
{
|
||||
poll_clients(netplay);
|
||||
}
|
||||
|
||||
if netplay.disconnected {
|
||||
netplay.requests.push_back(ggrs::GgrsRequest::AdvanceFrame {
|
||||
inputs: vec![
|
||||
(
|
||||
ui::input::InputData::default(),
|
||||
ggrs::InputStatus::Disconnected
|
||||
);
|
||||
4
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
match netplay.session.advance_frame() {
|
||||
Ok(requests) => {
|
||||
netplay.requests.extend(requests);
|
||||
@@ -300,56 +377,114 @@ fn advance_frame(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
server_addr: String,
|
||||
player_number: usize,
|
||||
number_of_players: usize,
|
||||
input_delay: usize,
|
||||
pal: bool,
|
||||
) -> Option<Netplay> {
|
||||
let (socket, loop_fut) = matchbox_socket::WebRtcSocketBuilder::new(server_addr)
|
||||
.add_unreliable_channel()
|
||||
.add_reliable_channel()
|
||||
.build();
|
||||
fn verify_peers(
|
||||
peers: &[matchbox_socket::PeerId],
|
||||
player_numbers: &std::collections::BTreeMap<usize, Option<matchbox_socket::PeerId>>,
|
||||
) -> bool {
|
||||
for (_, peer) in player_numbers.iter() {
|
||||
if let Some(peer) = peer
|
||||
&& !peers.contains(peer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn create_socket(builder: matchbox_socket::WebRtcSocketBuilder) -> matchbox_socket::WebRtcSocket {
|
||||
let (socket, loop_fut) = builder.build();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = loop_fut.await {
|
||||
eprintln!("WebRTC loop failed: {}", e);
|
||||
}
|
||||
});
|
||||
let mut matchbox_socket = MatchboxSocket(socket);
|
||||
let mut reliable_channel = matchbox_socket.0.take_channel(1).unwrap();
|
||||
let now = std::time::Instant::now();
|
||||
let timeout = std::time::Duration::from_secs(10);
|
||||
socket
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
device: &mut device::Device,
|
||||
netplay_config: &NetplayConfig,
|
||||
pal: bool,
|
||||
) -> Option<Netplay> {
|
||||
let mut builder = matchbox_socket::WebRtcSocketBuilder::new(&netplay_config.server_addr)
|
||||
.add_unreliable_channel()
|
||||
.add_reliable_channel();
|
||||
|
||||
if let Ok(ice_config) = std::fs::read(&netplay_config.ice_config_path)
|
||||
&& let Ok(ice_config) = serde_json::from_slice::<RtcIceServerConfig>(&ice_config)
|
||||
{
|
||||
builder = builder.ice_server(matchbox_socket::RtcIceServerConfig {
|
||||
urls: ice_config.urls,
|
||||
username: ice_config.username,
|
||||
credential: ice_config.credential,
|
||||
});
|
||||
} else {
|
||||
eprintln!("Using default ICE config");
|
||||
}
|
||||
|
||||
let mut socket = create_socket(builder.clone());
|
||||
|
||||
let mut now = std::time::Instant::now();
|
||||
let mut message_timer = now;
|
||||
let socket_timeout = std::time::Duration::from_secs_f64(rand::random_range(8.0..10.0));
|
||||
let mut player_numbers = std::collections::BTreeMap::new();
|
||||
player_numbers.insert(player_number, None);
|
||||
loop {
|
||||
matchbox_socket.0.update_peers();
|
||||
let peers = matchbox_socket
|
||||
.0
|
||||
|
||||
ui::video::onscreen_message(
|
||||
"Connecting to netplay peers...\nPlease wait...",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
|
||||
device.cpu.running = true;
|
||||
while device.cpu.running {
|
||||
socket.update_peers();
|
||||
let peers = socket
|
||||
.connected_peers()
|
||||
.collect::<Vec<matchbox_socket::PeerId>>();
|
||||
|
||||
send_player_number(&mut reliable_channel, peers, player_number);
|
||||
get_player_numbers(&mut reliable_channel, &mut player_numbers);
|
||||
if player_numbers.len() == number_of_players {
|
||||
break;
|
||||
if peers.len() == netplay_config.number_of_players - 1 {
|
||||
player_numbers.insert(netplay_config.player_number, None);
|
||||
send_player_number(socket.channel_mut(1), &peers, netplay_config.player_number);
|
||||
get_player_numbers(socket.channel_mut(1), &mut player_numbers);
|
||||
if player_numbers.len() == netplay_config.number_of_players
|
||||
&& verify_peers(&peers, &player_numbers)
|
||||
{
|
||||
break;
|
||||
}
|
||||
} else if now.elapsed() > socket_timeout {
|
||||
socket.close();
|
||||
player_numbers.clear();
|
||||
socket = create_socket(builder.clone());
|
||||
now = std::time::Instant::now();
|
||||
}
|
||||
|
||||
if now.elapsed() > timeout {
|
||||
eprintln!("Could not connect to netplay peers");
|
||||
return None;
|
||||
if message_timer.elapsed() > std::time::Duration::from_secs(4) {
|
||||
ui::video::onscreen_message(
|
||||
"Still connecting to netplay peers...\nPlease wait...",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
message_timer = std::time::Instant::now();
|
||||
}
|
||||
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
ui::video::render_frame();
|
||||
ui::video::update_screen();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
ui::video::check_callback(device);
|
||||
}
|
||||
if !device.cpu.running {
|
||||
// user closed the window
|
||||
return None;
|
||||
}
|
||||
device.cpu.running = false;
|
||||
|
||||
let mut session_builder = ggrs::SessionBuilder::<GgrsConfig>::new()
|
||||
.with_num_players(number_of_players)
|
||||
.with_num_players(netplay_config.number_of_players)
|
||||
.unwrap()
|
||||
.with_input_delay(input_delay)
|
||||
.with_input_delay(netplay_config.input_delay)
|
||||
.with_fps(if pal { 50 } else { 60 })
|
||||
.unwrap()
|
||||
.with_desync_detection_mode(ggrs::DesyncDetection::On { interval: 60 })
|
||||
.with_max_prediction_window(0)
|
||||
.with_max_prediction_window(16)
|
||||
.with_disconnect_timeout(std::time::Duration::from_secs(if cfg!(debug_assertions) {
|
||||
10
|
||||
} else {
|
||||
@@ -370,34 +505,62 @@ pub fn init(
|
||||
}
|
||||
}
|
||||
|
||||
let mut session = session_builder.start_p2p_session(matchbox_socket).unwrap();
|
||||
let matchbox_channel = MatchboxChannel {
|
||||
channel: socket.take_channel(0).unwrap(),
|
||||
};
|
||||
let reliable_channel = socket.take_channel(1).unwrap();
|
||||
|
||||
if matchbox_channel.channel.config().max_retransmits != Some(0)
|
||||
|| matchbox_channel.channel.config().ordered
|
||||
{
|
||||
eprintln!("Sending GGRS traffic over reliable channel");
|
||||
}
|
||||
|
||||
let mut session = session_builder.start_p2p_session(matchbox_channel).unwrap();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let timeout = std::time::Duration::from_secs(10);
|
||||
while session.current_state() != ggrs::SessionState::Running {
|
||||
session.poll_remote_clients();
|
||||
if now.elapsed() > timeout {
|
||||
eprintln!("Could not start netplay session");
|
||||
return None;
|
||||
}
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
Some(Netplay {
|
||||
disconnected: false,
|
||||
incoming_message: vec![],
|
||||
input_delay,
|
||||
input_delay: netplay_config.input_delay,
|
||||
session,
|
||||
reliable_channel,
|
||||
peers,
|
||||
player_number,
|
||||
player_number: netplay_config.player_number,
|
||||
connected: [
|
||||
number_of_players > 0,
|
||||
number_of_players > 1,
|
||||
number_of_players > 2,
|
||||
number_of_players > 3,
|
||||
netplay_config.number_of_players > 0,
|
||||
netplay_config.number_of_players > 1,
|
||||
netplay_config.number_of_players > 2,
|
||||
netplay_config.number_of_players > 3,
|
||||
],
|
||||
inputs: Vec::new(),
|
||||
requests: std::collections::VecDeque::new(),
|
||||
received_data: std::collections::VecDeque::new(),
|
||||
messages: std::collections::HashMap::new(),
|
||||
ice_config_path: netplay_config.ice_config_path.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close(netplay: &mut Netplay) {
|
||||
if !netplay.disconnected {
|
||||
let message = NetplayMessage {
|
||||
name: "disconnect".to_string(),
|
||||
data: vec![],
|
||||
};
|
||||
send_message(netplay, message);
|
||||
std::thread::sleep(std::time::Duration::from_millis(200)); // give the message time to be sent
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_file(&netplay.ice_config_path);
|
||||
}
|
||||
|
||||
@@ -150,10 +150,8 @@ pub fn load_game(
|
||||
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
|
||||
ra_load_game(rom.as_ptr(), rom_size, tx_ptr);
|
||||
};
|
||||
let join_handle = std::thread::spawn(move || {
|
||||
rx.blocking_recv().unwrap();
|
||||
});
|
||||
join_handle.join().unwrap();
|
||||
|
||||
rx.blocking_recv().unwrap();
|
||||
|
||||
let mut c_title = std::ptr::null();
|
||||
let mut c_image_url = std::ptr::null();
|
||||
|
||||
+21
-16
@@ -74,7 +74,6 @@ where
|
||||
pub struct Savestate {
|
||||
pub save_state: bool,
|
||||
pub load_state: bool,
|
||||
pub save_rewind: bool,
|
||||
pub load_rewind: bool,
|
||||
pub last_rewind_saved: f64,
|
||||
#[serde(skip)]
|
||||
@@ -96,18 +95,23 @@ static SAVES_CLONE: std::sync::LazyLock<std::sync::Mutex<ui::storage::Saves>> =
|
||||
std::sync::LazyLock::new(|| std::sync::Mutex::new(ui::storage::Saves::default()));
|
||||
|
||||
pub fn process_savestates(device: &mut device::Device) {
|
||||
if device.savestate.save_state || device.savestate.save_rewind {
|
||||
let rewind = device.savestate.save_rewind && !device.savestate.save_state;
|
||||
if rewind {
|
||||
device.savestate.save_rewind = false;
|
||||
}
|
||||
let save_rewind = if device.netplay.is_none()
|
||||
&& device.ui.config.emulation.rewind
|
||||
&& device.vi.elapsed_time - device.savestate.last_rewind_saved > 1.0
|
||||
{
|
||||
device.savestate.last_rewind_saved = device.vi.elapsed_time;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if device.savestate.save_state || save_rewind {
|
||||
create_savestate(device, !device.savestate.save_state, None);
|
||||
device.savestate.save_state = false;
|
||||
create_savestate(device, rewind, None);
|
||||
} else if device.savestate.load_state || device.savestate.load_rewind {
|
||||
load_savestate(device, device.savestate.load_rewind, None);
|
||||
device.savestate.load_state = false;
|
||||
let rewind = device.savestate.load_rewind;
|
||||
device.savestate.load_rewind = false;
|
||||
load_savestate(device, rewind, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,12 +245,11 @@ pub fn load_savestate(device: &mut device::Device, rewind: bool, rewind_frame: O
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path)
|
||||
&& let Ok(savestate) = &savestate
|
||||
&& let Ok(device_data) = ui::storage::decompress_file(savestate, "device")
|
||||
&& let Ok(saves_data) = ui::storage::decompress_file(savestate, "saves")
|
||||
&& let Ok(rdp_state) = ui::storage::decompress_file(savestate, "rdp_state")
|
||||
&& let Ok(ra_state) = ui::storage::decompress_file(savestate, "ra_state")
|
||||
} else if let Ok(savestate) = std::fs::read(&device.ui.storage.paths.savestate_file_path)
|
||||
&& let Ok(device_data) = ui::storage::decompress_file(&savestate, "device")
|
||||
&& let Ok(saves_data) = ui::storage::decompress_file(&savestate, "saves")
|
||||
&& let Ok(rdp_state) = ui::storage::decompress_file(&savestate, "rdp_state")
|
||||
&& let Ok(ra_state) = ui::storage::decompress_file(&savestate, "ra_state")
|
||||
&& let Ok(state) = postcard::from_bytes::<Box<device::Device>>(&device_data)
|
||||
&& let Ok(saves) = postcard::from_bytes(&saves_data)
|
||||
{
|
||||
@@ -263,7 +266,9 @@ pub fn load_savestate(device: &mut device::Device, rewind: bool, rewind_frame: O
|
||||
if let Some(mut state) = state_data
|
||||
&& device.rdram.size == state.device.rdram.size
|
||||
{
|
||||
ui::video::idle();
|
||||
if device.netplay.is_none() {
|
||||
ui::video::idle();
|
||||
}
|
||||
|
||||
device.savestate.last_rewind_saved = state.device.vi.elapsed_time;
|
||||
|
||||
|
||||
@@ -68,10 +68,10 @@ pub struct Storage {
|
||||
pub struct Video {
|
||||
pub window: *mut sdl3_sys::video::SDL_Window,
|
||||
pub fullscreen: bool,
|
||||
pub fps_tx: Option<tokio::sync::mpsc::Sender<bool>>,
|
||||
pub fps_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
|
||||
pub vis_tx: Option<tokio::sync::mpsc::Sender<bool>>,
|
||||
pub vis_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
|
||||
pub fps_tx: Option<tokio::sync::mpsc::UnboundedSender<bool>>,
|
||||
pub fps_rx: Option<tokio::sync::mpsc::UnboundedReceiver<bool>>,
|
||||
pub vis_tx: Option<tokio::sync::mpsc::UnboundedSender<bool>>,
|
||||
pub vis_rx: Option<tokio::sync::mpsc::UnboundedReceiver<bool>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Video {}
|
||||
@@ -79,8 +79,8 @@ unsafe impl Sync for Video {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Usb {
|
||||
pub usb_tx: Option<tokio::sync::broadcast::Sender<usb::UsbData>>,
|
||||
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
|
||||
pub usb_tx: Option<tokio::sync::mpsc::UnboundedSender<usb::UsbData>>,
|
||||
pub cart_rx: Option<tokio::sync::mpsc::UnboundedReceiver<usb::UsbData>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -191,8 +191,8 @@ impl Ui {
|
||||
pub fn new() -> Ui {
|
||||
let dirs = get_dirs();
|
||||
|
||||
let (fps_tx, fps_rx) = tokio::sync::mpsc::channel(1000);
|
||||
let (vis_tx, vis_rx) = tokio::sync::mpsc::channel(1000);
|
||||
let (fps_tx, fps_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (vis_tx, vis_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
Ui {
|
||||
input: Input {
|
||||
last_polled: 0,
|
||||
|
||||
+7
-2
@@ -1,4 +1,5 @@
|
||||
use crate::Args;
|
||||
use crate::create_runtime;
|
||||
use crate::run;
|
||||
use crate::ui;
|
||||
use clap::Parser;
|
||||
@@ -158,17 +159,21 @@ fn argv_to_strings(argc: std::ffi::c_int, argv: *mut *mut std::ffi::c_char) -> V
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[tokio::main]
|
||||
pub async extern "C" fn gopher64_sdl_main(
|
||||
pub extern "C" fn gopher64_sdl_main(
|
||||
argc: std::ffi::c_int,
|
||||
argv: *mut *mut std::ffi::c_char,
|
||||
) -> std::ffi::c_int {
|
||||
let (close_tx, handle) = create_runtime();
|
||||
let _guard = handle.enter();
|
||||
|
||||
let raw = argv_to_strings(argc, argv);
|
||||
let args = Args::try_parse_from(raw).unwrap();
|
||||
if let Err(err) = run(args, argc as usize) {
|
||||
close_tx.send(()).unwrap();
|
||||
eprintln!("Error running game: {err:?}");
|
||||
return 1;
|
||||
}
|
||||
close_tx.send(()).unwrap();
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
static ADJUST_LOCKED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
@@ -174,6 +175,10 @@ pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
|
||||
return;
|
||||
}
|
||||
|
||||
if netplay::in_rollback(device.netplay.as_ref()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
|
||||
let mut i = 0;
|
||||
while i < length as usize / 2 {
|
||||
|
||||
+12
-2
@@ -136,17 +136,27 @@ impl Config {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("config.json");
|
||||
let config_file = std::fs::read(file_path);
|
||||
let mut input_data: Option<Input> = None;
|
||||
if let Ok(config_file) = config_file {
|
||||
let result = serde_json::from_slice::<Config>(config_file.as_ref());
|
||||
if let Ok(mut result) = result {
|
||||
result.write_to_disk = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to restore input data from old config file
|
||||
if let Ok(value) = serde_json::from_slice::<serde_json::Value>(&config_file)
|
||||
&& let Some(data) = value.get("input")
|
||||
&& let Ok(data) = serde_json::from_value::<Input>(data.clone())
|
||||
{
|
||||
input_data = Some(data);
|
||||
}
|
||||
}
|
||||
|
||||
let mut input_profiles = std::collections::BTreeMap::new();
|
||||
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
|
||||
Config {
|
||||
input: Input {
|
||||
input: input_data.unwrap_or(Input {
|
||||
input_profile_binding: [
|
||||
"default".to_string(),
|
||||
"default".to_string(),
|
||||
@@ -160,7 +170,7 @@ impl Config {
|
||||
gb_rom_path: [String::new(), String::new(), String::new(), String::new()],
|
||||
gb_ram_path: [String::new(), String::new(), String::new(), String::new()],
|
||||
emulate_vru: false,
|
||||
},
|
||||
}),
|
||||
video: Video {
|
||||
upscale: 1,
|
||||
ssaa: false,
|
||||
|
||||
+17
-8
@@ -34,13 +34,18 @@ fn check_latest_version(weak: slint::Weak<AppWindow>) {
|
||||
if let Ok(response) = response {
|
||||
let data: Result<GithubData, reqwest::Error> = response.json().await;
|
||||
|
||||
let latest_version = if let Ok(data) = data
|
||||
&& let Ok(github_version) = semver::Version::parse(&data.tag_name[1..])
|
||||
{
|
||||
github_version
|
||||
} else {
|
||||
eprintln!("Error getting latest version from GitHub");
|
||||
semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
|
||||
let latest_version = match data {
|
||||
Ok(data) => match semver::Version::parse(&data.tag_name[1..]) {
|
||||
Ok(github_version) => github_version,
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing latest version from GitHub: {}", e);
|
||||
semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error getting latest version from GitHub: {}", e);
|
||||
semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
|
||||
}
|
||||
};
|
||||
let current_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||
if current_version < latest_version {
|
||||
@@ -535,11 +540,15 @@ pub fn app_window(app: &AppWindow, is_android: bool) {
|
||||
let weak_app = app.as_weak();
|
||||
app.window().on_close_requested(move || {
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| save_settings(&handle))
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle);
|
||||
handle.invoke_netplay_close();
|
||||
})
|
||||
.unwrap();
|
||||
slint::CloseRequestResponse::HideWindow
|
||||
});
|
||||
}
|
||||
|
||||
app.run().unwrap();
|
||||
retroachievements::shutdown_client();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export component AppWindow inherits Window {
|
||||
callback netplay_begin_game(int);
|
||||
callback netplay_send_chat_message(string);
|
||||
callback netplay_refresh_sessions;
|
||||
callback netplay_create_session(string, string, string, string, string, string);
|
||||
callback netplay_create_session(string, string, string, string, string, string, bool, bool);
|
||||
callback netplay_select_rom(string);
|
||||
callback netplay_join_session(string, string, string, string);
|
||||
callback netplay_close;
|
||||
@@ -119,6 +119,7 @@ export component AppWindow inherits Window {
|
||||
in-out property netplay_input_delay <=> NetplayWaitData.input_delay;
|
||||
in-out property netplay_recommended_delay <=> NetplayWaitData.recommended_delay;
|
||||
in-out property netplay_game_pal <=> NetplayWaitData.game_pal;
|
||||
in-out property netplay_connected_peers_count <=> NetplayWaitData.connected_peers_count;
|
||||
|
||||
private property <string> popup_message;
|
||||
private property <bool> popup_has_close;
|
||||
@@ -255,8 +256,8 @@ export component AppWindow inherits Window {
|
||||
select_rom(rom_dir) => {
|
||||
netplay_select_rom(rom_dir);
|
||||
}
|
||||
create_session(a, b, c, d, e, f) => {
|
||||
netplay_create_session(a,b,c,d,e,f);
|
||||
create_session(a, b, c, d, e, f, g, h) => {
|
||||
netplay_create_session(a,b,c,d,e,f,g,h);
|
||||
}
|
||||
close => {
|
||||
netplay_close();
|
||||
@@ -381,7 +382,10 @@ export component AppWindow inherits Window {
|
||||
NetplayWaitData.input_delay = 0;
|
||||
NetplayWaitData.recommended_delay = 0;
|
||||
NetplayWaitData.players = [];
|
||||
NetplayWaitData.connected_peers_count = 0;
|
||||
NetplayData.pending_session = false;
|
||||
NetplayData.pending_refresh = false;
|
||||
NetplayData.sessions = [];
|
||||
NetplayData.current_session = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Page } from "page.slint";
|
||||
export component NetplayCreate inherits Page {
|
||||
title: @tr("Netplay: Create Session");
|
||||
callback select_rom(string);
|
||||
callback create_session(string, string, string, string, string, string);
|
||||
callback create_session(string, string, string, string, string, string, bool, bool);
|
||||
callback close;
|
||||
|
||||
VerticalBox {
|
||||
@@ -98,7 +98,9 @@ export component NetplayCreate inherits Page {
|
||||
NetplayData.game_name,
|
||||
NetplayData.game_hash,
|
||||
NetplayData.game_cheats,
|
||||
NetplayData.password);
|
||||
NetplayData.password,
|
||||
SettingsData.overclock_n64_cpu,
|
||||
SettingsData.disable_expansion_pak);
|
||||
}
|
||||
}
|
||||
Button {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VerticalBox, HorizontalBox, TextEdit, ComboBox, GroupBox, LineEdit, Button, GridBox, ScrollView } from "std-widgets.slint";
|
||||
import { VerticalBox, HorizontalBox, TextEdit, SpinBox, GroupBox, LineEdit, Button, GridBox, ScrollView } from "std-widgets.slint";
|
||||
import { NetplayData } from "netplay_page.slint";
|
||||
import { Page } from "page.slint";
|
||||
|
||||
@@ -10,6 +10,7 @@ export global NetplayWaitData {
|
||||
in-out property <int> input_delay;
|
||||
in-out property <int> recommended_delay;
|
||||
in-out property <bool> game_pal;
|
||||
in-out property <int> connected_peers_count;
|
||||
}
|
||||
|
||||
export component NetplayWait inherits Page {
|
||||
@@ -90,7 +91,7 @@ export component NetplayWait inherits Page {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: NetplayWaitData.motd;
|
||||
text: NetplayData.player_name == NetplayWaitData.players[0] ? @tr("Setting the input delay too low will cause the game to lag.\nYou can lower the input delay in-game by pressing Alt+[ and raise it with Alt+].") : NetplayWaitData.motd;
|
||||
horizontal-alignment: center;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
@@ -102,12 +103,13 @@ export component NetplayWait inherits Page {
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
|
||||
current-index: NetplayWaitData.input_delay;
|
||||
SpinBox {
|
||||
minimum: 1;
|
||||
maximum: 16;
|
||||
value: NetplayWaitData.input_delay;
|
||||
enabled: NetplayWaitData.recommended_delay != 0;
|
||||
selected => {
|
||||
NetplayWaitData.input_delay = self.current-index;
|
||||
edited => {
|
||||
NetplayWaitData.input_delay = self.value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -121,8 +123,9 @@ export component NetplayWait inherits Page {
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("Start Game");
|
||||
enabled: NetplayWaitData.players[0] == NetplayData.player_name && NetplayWaitData.can_start && NetplayWaitData.recommended_delay != 0;
|
||||
property <bool> mesh_complete: NetplayWaitData.players.length == (NetplayWaitData.connected_peers_count + 1);
|
||||
text: mesh_complete ? @tr("Start Game") : @tr("Waiting for P2P mesh ") + (NetplayWaitData.connected_peers_count + 1) + "/" + NetplayWaitData.players.length;
|
||||
enabled: NetplayWaitData.players[0] == NetplayData.player_name && NetplayWaitData.can_start && NetplayWaitData.recommended_delay != 0 && mesh_complete;
|
||||
clicked => {
|
||||
NetplayWaitData.can_start = false;
|
||||
begin_game(NetplayWaitData.input_delay);
|
||||
|
||||
+312
-206
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::netplay::RtcIceServerConfig;
|
||||
use crate::ui;
|
||||
use crate::ui::gui::{AppWindow, open_uri, run_rom, save_settings};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
@@ -24,6 +25,12 @@ enum MessageType {
|
||||
ReceiveChatMessage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ServerAddresses {
|
||||
pub lobby: String,
|
||||
pub game: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct NetplaySession {
|
||||
password: Option<String>,
|
||||
@@ -33,8 +40,9 @@ struct NetplaySession {
|
||||
client_version: Option<String>,
|
||||
features: Option<std::collections::HashMap<String, String>>,
|
||||
players: Vec<String>,
|
||||
server_address: Option<String>,
|
||||
server_address: Option<ServerAddresses>,
|
||||
input_delay: Option<usize>,
|
||||
ice_server_config: Option<RtcIceServerConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -54,6 +62,7 @@ enum PingType {
|
||||
struct NetplayPingMessage {
|
||||
message_type: PingType,
|
||||
timestamp: u128,
|
||||
num_of_peers: usize,
|
||||
}
|
||||
|
||||
fn select_rom(weak: slint::Weak<AppWindow>, rom_dir: slint::SharedString) {
|
||||
@@ -99,39 +108,175 @@ fn select_rom(weak: slint::Weak<AppWindow>, rom_dir: slint::SharedString) {
|
||||
});
|
||||
}
|
||||
|
||||
fn setup_create_window(
|
||||
fn setup_callbacks(
|
||||
app: &AppWindow,
|
||||
game_settings: ui::GameSettings,
|
||||
netplay_write_sender: tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_sender: tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_write_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
close_ping_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
netplay_write_sender: &tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_receiver: &tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_sender: &tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_write_receiver: &tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
close_ping_tx: &tokio::sync::broadcast::Sender<()>,
|
||||
close_ping_rx: &tokio::sync::broadcast::Receiver<()>,
|
||||
) {
|
||||
let weak = app.as_weak();
|
||||
let netplay_write_sender_create_session = netplay_write_sender.clone();
|
||||
let netplay_read_receiver_create_session = netplay_read_receiver.resubscribe();
|
||||
let netplay_write_receiver_create_session = netplay_write_receiver.resubscribe();
|
||||
let netplay_read_sender_create_session = netplay_read_sender.clone();
|
||||
let close_ping_tx_create_session = close_ping_tx.clone();
|
||||
let close_ping_rx_create_session = close_ping_rx.resubscribe();
|
||||
app.on_netplay_create_session(
|
||||
move |session_name, player_name, game_name, game_hash, game_cheats, password| {
|
||||
move |session_name,
|
||||
player_name,
|
||||
game_name,
|
||||
game_hash,
|
||||
game_cheats,
|
||||
password,
|
||||
overclock,
|
||||
disable_expansion_pak| {
|
||||
close_connections(
|
||||
&netplay_write_sender_create_session,
|
||||
&netplay_read_sender_create_session,
|
||||
&close_ping_tx_create_session,
|
||||
);
|
||||
manage_websocket(
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
netplay_read_sender_create_session.clone(),
|
||||
netplay_write_receiver_create_session.resubscribe(),
|
||||
);
|
||||
|
||||
create_session(
|
||||
netplay_write_sender.clone(),
|
||||
netplay_read_receiver.resubscribe(),
|
||||
close_ping_rx.resubscribe(),
|
||||
netplay_write_sender_create_session.clone(),
|
||||
netplay_read_receiver_create_session.resubscribe(),
|
||||
close_ping_rx_create_session.resubscribe(),
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
game_cheats.to_string(),
|
||||
password.to_string(),
|
||||
game_settings.clone(),
|
||||
overclock,
|
||||
disable_expansion_pak,
|
||||
weak.clone(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let write_sender_chat_message = netplay_write_sender.clone();
|
||||
app.on_netplay_send_chat_message(move |message| {
|
||||
let send_chat = NetplayLobbyMessage {
|
||||
message_type: MessageType::SendChatMessage,
|
||||
sessions: std::collections::HashMap::new(),
|
||||
message: Some(message.into()),
|
||||
};
|
||||
write_sender_chat_message.send(Some(send_chat)).unwrap();
|
||||
});
|
||||
|
||||
let write_sender_begin_game = netplay_write_sender.clone();
|
||||
app.on_netplay_begin_game(move |chosen_input_delay| {
|
||||
let begin_game = NetplayLobbyMessage {
|
||||
message_type: MessageType::RequestBeginGame,
|
||||
sessions: std::collections::HashMap::new(),
|
||||
message: Some(chosen_input_delay.to_string()),
|
||||
};
|
||||
write_sender_begin_game.send(Some(begin_game)).unwrap();
|
||||
});
|
||||
|
||||
let write_sender_refresh_sessions = netplay_write_sender.clone();
|
||||
app.on_netplay_refresh_sessions(move || {
|
||||
update_sessions(write_sender_refresh_sessions.clone());
|
||||
});
|
||||
|
||||
let write_sender_join_session = netplay_write_sender.clone();
|
||||
app.on_netplay_join_session(move |session_name, player_name, game_hash, password| {
|
||||
join_session(
|
||||
write_sender_join_session.clone(),
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
password.to_string(),
|
||||
);
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
app.on_create_session_button_clicked(move || {
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle);
|
||||
setup_create_window(&handle);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
let netplay_write_sender_join_session_button = netplay_write_sender.clone();
|
||||
let netplay_read_receiver_join_session_button = netplay_read_receiver.resubscribe();
|
||||
let netplay_read_sender_join_session_button = netplay_read_sender.clone();
|
||||
let netplay_write_receiver_join_session_button = netplay_write_receiver.resubscribe();
|
||||
let close_ping_tx_join_session_button = close_ping_tx.clone();
|
||||
let close_ping_rx_join_session_button = close_ping_rx.resubscribe();
|
||||
app.on_join_session_button_clicked(move || {
|
||||
let netplay_write_sender = netplay_write_sender_join_session_button.clone();
|
||||
let netplay_read_receiver = netplay_read_receiver_join_session_button.resubscribe();
|
||||
let netplay_read_sender = netplay_read_sender_join_session_button.clone();
|
||||
let netplay_write_receiver = netplay_write_receiver_join_session_button.resubscribe();
|
||||
let close_ping_tx = close_ping_tx_join_session_button.clone();
|
||||
let close_ping_rx = close_ping_rx_join_session_button.resubscribe();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle);
|
||||
setup_join_window(
|
||||
&handle,
|
||||
netplay_write_sender,
|
||||
netplay_read_receiver,
|
||||
netplay_read_sender,
|
||||
netplay_write_receiver,
|
||||
close_ping_tx,
|
||||
close_ping_rx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
app.on_netplay_select_rom(move |rom_dir| {
|
||||
select_rom(weak_app.clone(), rom_dir);
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
let netplay_write_sender = netplay_write_sender.clone();
|
||||
let netplay_read_sender = netplay_read_sender.clone();
|
||||
let close_ping_tx = close_ping_tx.clone();
|
||||
app.on_netplay_close(move || {
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_show_netplay_wait_session(false);
|
||||
handle.set_show_netplay_create_session(false);
|
||||
handle.set_show_netplay_join_session(false);
|
||||
handle.invoke_clear_netplay_data();
|
||||
handle.invoke_close_message();
|
||||
})
|
||||
.unwrap();
|
||||
close_connections(&netplay_write_sender, &netplay_read_sender, &close_ping_tx);
|
||||
});
|
||||
|
||||
app.on_netplay_discord_button_clicked(move || {
|
||||
open_uri("https://discord.gg/JyW6ZgBUyS");
|
||||
});
|
||||
app.on_netplay_feedback_button_clicked(move || {
|
||||
open_uri("https://github.com/gopher64/gopher64/discussions/453");
|
||||
});
|
||||
}
|
||||
|
||||
fn close_connections(
|
||||
netplay_write_sender: &tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_sender: &tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
close_ping_tx: &tokio::sync::broadcast::Sender<()>,
|
||||
) {
|
||||
let _ = netplay_write_sender.send(None); // close current websocket if any
|
||||
let _ = netplay_read_sender.send(None); // close current receiver if any
|
||||
let _ = close_ping_tx.send(()); // close ping
|
||||
}
|
||||
|
||||
fn setup_create_window(app: &AppWindow) {
|
||||
app.set_show_netplay_create_session(true);
|
||||
}
|
||||
|
||||
@@ -139,67 +284,86 @@ fn manage_websocket(
|
||||
netplay_read_sender: tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
mut netplay_write_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
) {
|
||||
let mut request = "wss://netplay.gopher64.com".into_client_request().unwrap();
|
||||
let mut request = std::env::var("NETPLAY_SERVER_URL")
|
||||
.unwrap_or("wss://netplay.gopher64.com".to_string())
|
||||
.into_client_request()
|
||||
.unwrap();
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("Authorization", env!("NETPLAY_ID").parse().unwrap());
|
||||
tokio::spawn(async move {
|
||||
if let Ok(Ok((socket, _response))) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(3),
|
||||
tokio_tungstenite::connect_async(request),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let (mut write, mut read) = socket.split();
|
||||
Ok(Ok((socket, _response))) => {
|
||||
let (mut write, mut read) = socket.split();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(Ok(response)) = read.next().await {
|
||||
let decoded_response =
|
||||
postcard::from_bytes::<NetplayLobbyMessage>(&response.into_data());
|
||||
match decoded_response {
|
||||
Ok(message) => {
|
||||
let _ = netplay_read_sender.send(Some(message));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse message: {}", e);
|
||||
tokio::spawn(async move {
|
||||
while let Some(Ok(response)) = read.next().await {
|
||||
match response {
|
||||
Message::Binary(data) => {
|
||||
let decoded_response =
|
||||
postcard::from_bytes::<NetplayLobbyMessage>(&data);
|
||||
match decoded_response {
|
||||
Ok(message) => {
|
||||
let _ = netplay_read_sender.send(Some(message));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse message: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Close(_) => {
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match netplay_write_receiver.recv().await {
|
||||
Ok(Some(response)) => {
|
||||
if let Err(e) = write
|
||||
.send(Message::Binary(Bytes::from(
|
||||
postcard::to_stdvec(&response).unwrap(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to send message: {}", e);
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match netplay_write_receiver.recv().await {
|
||||
Ok(Some(response)) => {
|
||||
if let Err(e) = write
|
||||
.send(Message::Binary(Bytes::from(
|
||||
postcard::to_stdvec(&response).unwrap(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to send message: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
|
||||
panic!("netplay_write_receiver lagged");
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
||||
break; // exit the loop if the receiver is closed
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
|
||||
panic!("netplay_write_receiver lagged");
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
||||
break; // exit the loop if the receiver is closed
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(e) = write
|
||||
.send(Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: Utf8Bytes::from(""),
|
||||
})))
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to send close message: {}", e);
|
||||
}
|
||||
});
|
||||
if let Err(e) = write
|
||||
.send(Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: Utf8Bytes::from(""),
|
||||
})))
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to send close message: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("Failed to connect to netplay: {}", e);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to connect to netplay: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -227,7 +391,8 @@ fn create_session(
|
||||
game_hash: String,
|
||||
game_cheats: String,
|
||||
password: String,
|
||||
game_settings: ui::GameSettings,
|
||||
overclock: bool,
|
||||
disable_expansion_pak: bool,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
@@ -237,10 +402,10 @@ fn create_session(
|
||||
features.insert("cheats".to_string(), game_cheats);
|
||||
}
|
||||
|
||||
features.insert("overclock".to_string(), game_settings.overclock.to_string());
|
||||
features.insert("overclock".to_string(), overclock.to_string());
|
||||
features.insert(
|
||||
"disable_expansion_pak".to_string(),
|
||||
game_settings.disable_expansion_pak.to_string(),
|
||||
disable_expansion_pak.to_string(),
|
||||
);
|
||||
|
||||
let session = NetplaySession {
|
||||
@@ -257,6 +422,7 @@ fn create_session(
|
||||
players: vec![player_name],
|
||||
server_address: None,
|
||||
input_delay: None,
|
||||
ice_server_config: None,
|
||||
};
|
||||
let create_session = NetplayLobbyMessage {
|
||||
message_type: MessageType::RequestCreateSession,
|
||||
@@ -267,7 +433,7 @@ fn create_session(
|
||||
netplay_write_sender.send(Some(create_session)).unwrap();
|
||||
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
std::time::Duration::from_secs(3),
|
||||
netplay_read_receiver.recv(),
|
||||
)
|
||||
.await
|
||||
@@ -304,6 +470,7 @@ fn create_session(
|
||||
close_ping_rx,
|
||||
session.server_address.as_ref().unwrap().clone(),
|
||||
session_name.into(),
|
||||
session.ice_server_config.clone(),
|
||||
session.game_name.as_ref().unwrap().into(),
|
||||
handle.get_netplay_rom_path(),
|
||||
ui::GameSettings {
|
||||
@@ -331,11 +498,14 @@ fn create_session(
|
||||
Ok(Err(err)) => {
|
||||
panic!("netplay_read_receiver error: {err}");
|
||||
}
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_netplay_pending_session(false);
|
||||
handle.invoke_show_message("Server did not respond".into(), true);
|
||||
handle.invoke_show_message(
|
||||
format!("Server did not respond: {e}").into(),
|
||||
true,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -364,6 +534,7 @@ fn join_session(
|
||||
players: vec![player_name],
|
||||
server_address: None,
|
||||
input_delay: None,
|
||||
ice_server_config: None,
|
||||
};
|
||||
let join_session = NetplayLobbyMessage {
|
||||
message_type: MessageType::RequestJoinSession,
|
||||
@@ -376,12 +547,22 @@ fn join_session(
|
||||
|
||||
fn update_ping(
|
||||
server_addr: String,
|
||||
ice_server_config: Option<RtcIceServerConfig>,
|
||||
mut close_ping_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
) {
|
||||
let (mut socket, loop_fut) = matchbox_socket::WebRtcSocketBuilder::new(server_addr)
|
||||
.add_unreliable_channel()
|
||||
.build();
|
||||
let mut builder =
|
||||
matchbox_socket::WebRtcSocketBuilder::new(server_addr).add_unreliable_channel();
|
||||
if let Some(ice_server_config) = ice_server_config {
|
||||
builder = builder.ice_server(matchbox_socket::RtcIceServerConfig {
|
||||
urls: ice_server_config.urls,
|
||||
username: ice_server_config.username,
|
||||
credential: ice_server_config.credential,
|
||||
});
|
||||
} else {
|
||||
eprintln!("Using default ICE config");
|
||||
}
|
||||
let (mut socket, loop_fut) = builder.build();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = loop_fut.await {
|
||||
@@ -392,9 +573,16 @@ fn update_ping(
|
||||
let (mut write, mut read) = channel.split();
|
||||
let mut write_clone = write.clone();
|
||||
let mut close_ping_rx_clone = close_ping_rx.resubscribe();
|
||||
let weak_app_clone = weak_app.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
socket.update_peers();
|
||||
let peers_count = socket.connected_peers().count();
|
||||
weak_app_clone
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_netplay_connected_peers_count(peers_count as i32);
|
||||
})
|
||||
.unwrap();
|
||||
for peer in socket.connected_peers() {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
@@ -403,6 +591,7 @@ fn update_ping(
|
||||
let ping_message = NetplayPingMessage {
|
||||
message_type: PingType::Ping,
|
||||
timestamp: now,
|
||||
num_of_peers: peers_count,
|
||||
};
|
||||
let data = postcard::to_stdvec(&ping_message).unwrap();
|
||||
let _ = write.send((peer, data.into())).await;
|
||||
@@ -450,6 +639,7 @@ fn update_ping(
|
||||
let pong_message = NetplayPingMessage {
|
||||
message_type: PingType::Pong,
|
||||
timestamp: message.timestamp,
|
||||
num_of_peers: message.num_of_peers,
|
||||
};
|
||||
let data = postcard::to_stdvec(&pong_message).unwrap();
|
||||
let _ = write_clone.send((peer, data.into())).await;
|
||||
@@ -459,13 +649,13 @@ fn update_ping(
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
let ping = (now - message.timestamp) / 2; // calculate one-way latency
|
||||
let ping = (now - message.timestamp) as f64 / 2.0;
|
||||
pings.push(ping);
|
||||
if pings.len() > 12 {
|
||||
// once we have at least 12 samples, remove the highest 2 and return the next highest
|
||||
pings.sort();
|
||||
pings.truncate(pings.len() - 2);
|
||||
let ping = *pings.last().unwrap();
|
||||
if pings.len() > message.num_of_peers * 4 {
|
||||
// once we have enough samples, average the 3 highest values
|
||||
pings.sort_by(f64::total_cmp);
|
||||
pings.pop(); // Remove highest value to avoid outliers
|
||||
let ping_avg = pings.iter().rev().take(3).sum::<f64>() / 3.0;
|
||||
pings.clear();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
@@ -474,8 +664,9 @@ fn update_ping(
|
||||
} else {
|
||||
60.0
|
||||
};
|
||||
let latency_frames = (ping_avg / (1000.0 / refresh_rate)).ceil() as i32;
|
||||
let recommendation =
|
||||
(ping as f64 / (1000.0 / refresh_rate)) as i32 + 1;
|
||||
(latency_frames + 1).min(16);
|
||||
|
||||
if handle.get_netplay_recommended_delay() == 0 {
|
||||
handle.set_netplay_input_delay(recommendation);
|
||||
@@ -496,19 +687,42 @@ fn update_ping(
|
||||
});
|
||||
}
|
||||
|
||||
fn update_ice_config(ice_server_config: Option<RtcIceServerConfig>) {
|
||||
tokio::spawn(async move {
|
||||
let ice_config_path = ui::get_dirs().cache_dir.join("ice_config.json");
|
||||
if let Some(ice_server_config) = ice_server_config {
|
||||
tokio::fs::write(
|
||||
&ice_config_path,
|
||||
serde_json::to_vec(&ice_server_config).unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
let _ = tokio::fs::remove_file(&ice_config_path).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn setup_wait_window(
|
||||
netplay_write_sender: tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
mut netplay_read_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
close_ping_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
server_addr: String,
|
||||
server_addresses: ServerAddresses,
|
||||
session_name: slint::SharedString,
|
||||
ice_server_config: Option<RtcIceServerConfig>,
|
||||
game_name: slint::SharedString,
|
||||
rom_path: slint::SharedString,
|
||||
game_settings: ui::GameSettings,
|
||||
app: &AppWindow,
|
||||
) {
|
||||
update_ping(server_addr.clone(), close_ping_rx, app.as_weak());
|
||||
update_ice_config(ice_server_config.clone());
|
||||
update_ping(
|
||||
server_addresses.lobby.clone(),
|
||||
ice_server_config,
|
||||
close_ping_rx,
|
||||
app.as_weak(),
|
||||
);
|
||||
|
||||
app.set_netplay_session_name(session_name);
|
||||
app.set_netplay_game_name(game_name);
|
||||
@@ -522,26 +736,6 @@ fn setup_wait_window(
|
||||
};
|
||||
netplay_write_sender.send(Some(request_update)).unwrap();
|
||||
|
||||
let sender = netplay_write_sender.clone();
|
||||
app.on_netplay_send_chat_message(move |message| {
|
||||
let send_chat = NetplayLobbyMessage {
|
||||
message_type: MessageType::SendChatMessage,
|
||||
sessions: std::collections::HashMap::new(),
|
||||
message: Some(message.into()),
|
||||
};
|
||||
sender.send(Some(send_chat)).unwrap();
|
||||
});
|
||||
|
||||
let sender = netplay_write_sender.clone();
|
||||
app.on_netplay_begin_game(move |chosen_input_delay| {
|
||||
let begin_game = NetplayLobbyMessage {
|
||||
message_type: MessageType::RequestBeginGame,
|
||||
sessions: std::collections::HashMap::new(),
|
||||
message: Some(chosen_input_delay.to_string()),
|
||||
};
|
||||
sender.send(Some(begin_game)).unwrap();
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
@@ -602,7 +796,7 @@ fn setup_wait_window(
|
||||
load_savestate_slot: None,
|
||||
},
|
||||
Some(ui::gui::NetplayDevice {
|
||||
server_addr,
|
||||
server_addr: server_addresses.game.clone(),
|
||||
player_number,
|
||||
number_of_players: players.row_count(),
|
||||
input_delay,
|
||||
@@ -652,8 +846,10 @@ fn setup_join_window(
|
||||
netplay_read_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
netplay_read_sender: tokio::sync::broadcast::Sender<Option<NetplayLobbyMessage>>,
|
||||
netplay_write_receiver: tokio::sync::broadcast::Receiver<Option<NetplayLobbyMessage>>,
|
||||
close_ping_tx: tokio::sync::broadcast::Sender<()>,
|
||||
close_ping_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) {
|
||||
close_connections(&netplay_write_sender, &netplay_read_sender, &close_ping_tx);
|
||||
manage_websocket(
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
@@ -661,22 +857,6 @@ fn setup_join_window(
|
||||
|
||||
app.set_netplay_pending_refresh(true);
|
||||
|
||||
let write_sender = netplay_write_sender.clone();
|
||||
app.on_netplay_refresh_sessions(move || {
|
||||
update_sessions(write_sender.clone());
|
||||
});
|
||||
|
||||
let write_sender = netplay_write_sender.clone();
|
||||
app.on_netplay_join_session(move |session_name, player_name, game_hash, password| {
|
||||
join_session(
|
||||
write_sender.clone(),
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
password.to_string(),
|
||||
);
|
||||
});
|
||||
|
||||
let weak = app.as_weak();
|
||||
let mut read_receiver = netplay_read_receiver.resubscribe();
|
||||
let write_sender = netplay_write_sender.clone();
|
||||
@@ -717,6 +897,7 @@ fn setup_join_window(
|
||||
close_ping_rx.resubscribe(),
|
||||
session.server_address.as_ref().unwrap().clone(),
|
||||
session_name.into(),
|
||||
session.ice_server_config.clone(),
|
||||
session.game_name.as_ref().unwrap().into(),
|
||||
handle.get_netplay_rom_path(),
|
||||
ui::GameSettings {
|
||||
@@ -793,10 +974,13 @@ fn setup_join_window(
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_netplay_pending_session(false);
|
||||
handle.invoke_show_message("Server did not respond".into(), true);
|
||||
handle.invoke_show_message(
|
||||
format!("Server did not respond: {e}").into(),
|
||||
true,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -824,91 +1008,13 @@ pub fn netplay_window(app: &AppWindow) {
|
||||
tokio::sync::broadcast::Receiver<()>,
|
||||
) = tokio::sync::broadcast::channel(5);
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
let write_sender_create = netplay_write_sender.clone();
|
||||
let read_receiver_create = netplay_read_receiver.resubscribe();
|
||||
let read_sender_create = netplay_read_sender.clone();
|
||||
let write_receiver_create = netplay_write_receiver.resubscribe();
|
||||
let close_ping_create = close_ping_rx.resubscribe();
|
||||
app.on_create_session_button_clicked(move || {
|
||||
let write_sender = write_sender_create.clone();
|
||||
let read_receiver = read_receiver_create.resubscribe();
|
||||
let read_sender = read_sender_create.clone();
|
||||
let write_receiver = write_receiver_create.resubscribe();
|
||||
let close_ping_rx = close_ping_create.resubscribe();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle);
|
||||
setup_create_window(
|
||||
&handle,
|
||||
ui::GameSettings {
|
||||
overclock: handle.get_overclock_n64_cpu(),
|
||||
disable_expansion_pak: handle.get_disable_expansion_pak(),
|
||||
cheats: rustc_hash::FxHashMap::default(), // not used here
|
||||
load_savestate_slot: None,
|
||||
},
|
||||
write_sender,
|
||||
read_receiver,
|
||||
read_sender,
|
||||
write_receiver,
|
||||
close_ping_rx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
let write_sender_join = netplay_write_sender.clone();
|
||||
let read_sender_join = netplay_read_sender.clone();
|
||||
let close_ping_join = close_ping_rx.resubscribe();
|
||||
app.on_join_session_button_clicked(move || {
|
||||
let write_sender = write_sender_join.clone();
|
||||
let read_receiver = netplay_read_receiver.resubscribe();
|
||||
let read_sender = read_sender_join.clone();
|
||||
let write_receiver = netplay_write_receiver.resubscribe();
|
||||
let close_ping_rx = close_ping_join.resubscribe();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle);
|
||||
setup_join_window(
|
||||
&handle,
|
||||
write_sender,
|
||||
read_receiver,
|
||||
read_sender,
|
||||
write_receiver,
|
||||
close_ping_rx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
app.on_netplay_select_rom(move |rom_dir| {
|
||||
select_rom(weak_app.clone(), rom_dir);
|
||||
});
|
||||
|
||||
let weak_app = app.as_weak();
|
||||
let write_sender = netplay_write_sender.clone();
|
||||
let read_sender = netplay_read_sender.clone();
|
||||
app.on_netplay_close(move || {
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_show_netplay_wait_session(false);
|
||||
handle.set_show_netplay_create_session(false);
|
||||
handle.set_show_netplay_join_session(false);
|
||||
handle.invoke_clear_netplay_data();
|
||||
handle.invoke_close_message();
|
||||
})
|
||||
.unwrap();
|
||||
let _ = write_sender.send(None); // close current websocket if any
|
||||
let _ = read_sender.send(None); // close current receiver if any
|
||||
let _ = close_ping_tx.send(()); // close ping
|
||||
});
|
||||
|
||||
app.on_netplay_discord_button_clicked(move || {
|
||||
open_uri("https://discord.gg/JyW6ZgBUyS");
|
||||
});
|
||||
app.on_netplay_feedback_button_clicked(move || {
|
||||
open_uri("https://github.com/gopher64/gopher64/discussions/453");
|
||||
});
|
||||
setup_callbacks(
|
||||
app,
|
||||
&netplay_write_sender,
|
||||
&netplay_read_receiver,
|
||||
&netplay_read_sender,
|
||||
&netplay_write_receiver,
|
||||
&close_ping_tx,
|
||||
&close_ping_rx,
|
||||
);
|
||||
}
|
||||
|
||||
+78
-79
@@ -11,7 +11,7 @@ pub struct UsbData {
|
||||
pub data_size: u32,
|
||||
}
|
||||
|
||||
fn respond_to_handshake(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, data: Vec<u8>) {
|
||||
fn respond_to_handshake(usb_tx: &tokio::sync::mpsc::UnboundedSender<UsbData>, data: Vec<u8>) {
|
||||
if let Ok(data) = String::from_utf8(data)
|
||||
&& data == "N64"
|
||||
{
|
||||
@@ -29,98 +29,98 @@ fn respond_to_handshake(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, data:
|
||||
async fn handle_connection(
|
||||
conn: tokio::net::TcpStream,
|
||||
mut shutdown_rx: tokio::sync::watch::Receiver<()>,
|
||||
mut usb_rx: tokio::sync::broadcast::Receiver<UsbData>,
|
||||
usb_tx: tokio::sync::broadcast::Sender<UsbData>,
|
||||
cart_tx: tokio::sync::broadcast::Sender<UsbData>,
|
||||
usb_rx: &mut tokio::sync::mpsc::UnboundedReceiver<UsbData>,
|
||||
usb_tx: tokio::sync::mpsc::UnboundedSender<UsbData>,
|
||||
cart_tx: tokio::sync::mpsc::UnboundedSender<UsbData>,
|
||||
) {
|
||||
let (mut incoming, mut outgoing) = conn.into_split();
|
||||
|
||||
let mut shutdown_rx_clone = shutdown_rx.clone();
|
||||
|
||||
while usb_rx.try_recv().is_ok() {} // drain stale USB messages
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut incoming_buffer = vec![0u8; 4096];
|
||||
let mut data_type: Option<u32> = None;
|
||||
let mut data_size: Option<u32> = None;
|
||||
let mut usb_buffer: Vec<u8> = vec![];
|
||||
loop {
|
||||
tokio::select! {
|
||||
data = usb_rx.recv() => {
|
||||
match data {
|
||||
Ok(data) => {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
output.extend_from_slice(&data.data_type.to_be_bytes());
|
||||
output.extend_from_slice(&data.data_size.to_be_bytes());
|
||||
output.extend_from_slice(&data.data);
|
||||
if outgoing.write_all(&output).await.is_err() {
|
||||
break;
|
||||
result = incoming.read(&mut incoming_buffer) => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
usb_buffer.extend_from_slice(&incoming_buffer[0..n]);
|
||||
if data_type.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_type = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if data_type.is_some() && data_size.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_size = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if let Some(d_type) = data_type && let Some(d_size) = data_size {
|
||||
let length = d_size as usize;
|
||||
if usb_buffer.len() >= length {
|
||||
let usb_data = UsbData {
|
||||
data: usb_buffer[0..length].to_vec(),
|
||||
data_type: d_type,
|
||||
data_size: d_size,
|
||||
};
|
||||
usb_buffer.drain(0..length);
|
||||
if usb_data.data_type == DATATYPE_TCPTEST {
|
||||
respond_to_handshake(&usb_tx,usb_data.data);
|
||||
} else if usb_data.data_type == DATATYPE_ROMUPLOAD {
|
||||
panic!("ROM upload not supported");
|
||||
} else {
|
||||
cart_tx.send(usb_data).unwrap();
|
||||
}
|
||||
data_type = None;
|
||||
data_size = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
|
||||
panic!("usb_rx lagged");
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
||||
break;
|
||||
Err(_e) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown_rx_clone.changed() => {
|
||||
_ = shutdown_rx.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut incoming_buffer = vec![0u8; 4096];
|
||||
let mut data_type: Option<u32> = None;
|
||||
let mut data_size: Option<u32> = None;
|
||||
let mut usb_buffer: Vec<u8> = vec![];
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = incoming.read(&mut incoming_buffer) => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
usb_buffer.extend_from_slice(&incoming_buffer[0..n]);
|
||||
if data_type.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_type = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if data_type.is_some() && data_size.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_size = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if let Some(d_type) = data_type && let Some(d_size) = data_size {
|
||||
let length = d_size as usize;
|
||||
if usb_buffer.len() >= length {
|
||||
let usb_data = UsbData {
|
||||
data: usb_buffer[0..length].to_vec(),
|
||||
data_type: d_type,
|
||||
data_size: d_size,
|
||||
};
|
||||
usb_buffer.drain(0..length);
|
||||
if usb_data.data_type == DATATYPE_TCPTEST {
|
||||
respond_to_handshake(&usb_tx,usb_data.data);
|
||||
} else if usb_data.data_type == DATATYPE_ROMUPLOAD {
|
||||
panic!("ROM upload not supported");
|
||||
} else {
|
||||
cart_tx.send(usb_data).unwrap();
|
||||
}
|
||||
data_type = None;
|
||||
data_size = None;
|
||||
}
|
||||
data = usb_rx.recv() => {
|
||||
match data {
|
||||
Some(data) => {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
output.extend_from_slice(&data.data_type.to_be_bytes());
|
||||
output.extend_from_slice(&data.data_size.to_be_bytes());
|
||||
output.extend_from_slice(&data.data);
|
||||
if outgoing.write_all(&output).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
break;
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown_rx.changed() => {
|
||||
_ = shutdown_rx_clone.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -133,24 +133,23 @@ pub fn init() -> (
|
||||
ui::Usb,
|
||||
) {
|
||||
let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(());
|
||||
let (usb_tx, usb_rx): (
|
||||
tokio::sync::broadcast::Sender<UsbData>,
|
||||
tokio::sync::broadcast::Receiver<UsbData>,
|
||||
) = tokio::sync::broadcast::channel(1024);
|
||||
let (usb_tx, mut usb_rx): (
|
||||
tokio::sync::mpsc::UnboundedSender<UsbData>,
|
||||
tokio::sync::mpsc::UnboundedReceiver<UsbData>,
|
||||
) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (cart_tx, cart_rx): (
|
||||
tokio::sync::broadcast::Sender<UsbData>,
|
||||
tokio::sync::broadcast::Receiver<UsbData>,
|
||||
) = tokio::sync::broadcast::channel(1024);
|
||||
tokio::sync::mpsc::UnboundedSender<UsbData>,
|
||||
tokio::sync::mpsc::UnboundedReceiver<UsbData>,
|
||||
) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let usb_tx_clone = usb_tx.clone();
|
||||
let cart_rx_clone = cart_rx.resubscribe();
|
||||
let handle = tokio::spawn(async move {
|
||||
if let Ok(listener) = tokio::net::TcpListener::bind("localhost:48646").await {
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = listener.accept() => {
|
||||
if let Ok((c,_)) = res {
|
||||
handle_connection(c,shutdown_rx.clone(),usb_rx.resubscribe(),usb_tx.clone(),cart_tx.clone()).await;
|
||||
handle_connection(c,shutdown_rx.clone(),&mut usb_rx,usb_tx.clone(),cart_tx.clone()).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -169,7 +168,7 @@ pub fn init() -> (
|
||||
Some(handle),
|
||||
ui::Usb {
|
||||
usb_tx: Some(usb_tx_clone),
|
||||
cart_rx: Some(cart_rx_clone),
|
||||
cart_rx: Some(cart_rx),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -193,6 +192,6 @@ pub fn close(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_to_usb(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, buffer: UsbData) {
|
||||
pub fn send_to_usb(usb_tx: &tokio::sync::mpsc::UnboundedSender<UsbData>, buffer: UsbData) {
|
||||
usb_tx.send(buffer).unwrap();
|
||||
}
|
||||
|
||||
+6
-6
@@ -10,7 +10,7 @@ const NTSC_WIDESCREEN_WIDTH: i32 = 426;
|
||||
const NTSC_STANDARD_WIDTH: i32 = 320;
|
||||
const NTSC_HEIGHT: i32 = 240;
|
||||
|
||||
fn build_gfx_info(device: &mut device::Device) -> GFX_INFO {
|
||||
fn build_gfx_info(device: &mut device::Device, netplay: bool) -> GFX_INFO {
|
||||
GFX_INFO {
|
||||
RDRAM: device.rdram.mem.as_mut_ptr(),
|
||||
DMEM: device.rsp.mem.as_mut_ptr(),
|
||||
@@ -22,7 +22,7 @@ fn build_gfx_info(device: &mut device::Device) -> GFX_INFO {
|
||||
PAL: device.cart.pal,
|
||||
widescreen: device.ui.config.video.widescreen,
|
||||
fullscreen: device.ui.video.fullscreen,
|
||||
vsync: if device.netplay.is_none() {
|
||||
vsync: if !netplay {
|
||||
device.ui.config.video.vsync
|
||||
} else {
|
||||
false
|
||||
@@ -34,7 +34,7 @@ fn build_gfx_info(device: &mut device::Device) -> GFX_INFO {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
pub fn init(device: &mut device::Device, netplay: bool) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
ui::ttf_init();
|
||||
|
||||
@@ -99,7 +99,7 @@ pub fn init(device: &mut device::Device) {
|
||||
sdl3_sys::everything::SDL_HideCursor();
|
||||
}
|
||||
|
||||
let gfx_info = build_gfx_info(device);
|
||||
let gfx_info = build_gfx_info(device, netplay);
|
||||
|
||||
unsafe {
|
||||
let font_bytes = include_bytes!("../../data/ui/RobotoMono-Regular.ttf");
|
||||
@@ -170,7 +170,7 @@ pub fn idle() {
|
||||
}
|
||||
|
||||
pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
|
||||
let gfx_info = build_gfx_info(device);
|
||||
let gfx_info = build_gfx_info(device, device.netplay.is_some());
|
||||
unsafe {
|
||||
rdp_load_state(gfx_info, rdp_state);
|
||||
for reg in 0..device::vi::VI_REGS_COUNT {
|
||||
@@ -247,7 +247,7 @@ pub fn check_callback(device: &mut device::Device) -> (bool, bool) {
|
||||
if let Some(netplay) = &mut device.netplay
|
||||
&& netplay.player_number == 0
|
||||
{
|
||||
if callback.decrease_input_delay && netplay.input_delay > 0 {
|
||||
if callback.decrease_input_delay {
|
||||
netplay::send_input_delay(netplay, netplay.input_delay - 1);
|
||||
} else if callback.increase_input_delay {
|
||||
netplay::send_input_delay(netplay, netplay.input_delay + 1);
|
||||
|
||||
+2
-2
@@ -7,13 +7,13 @@ pub fn prompt_for_match(words: &[String], frame_time: f64) -> u16 {
|
||||
dedup_words.sort();
|
||||
dedup_words.dedup();
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let vru_dialog = gui::VruDialog::new().unwrap();
|
||||
let vru_dialog_weak = vru_dialog.as_weak();
|
||||
|
||||
vru_dialog.on_vru_button_clicked(move |chosen_word| {
|
||||
tx.try_send(chosen_word.to_string()).unwrap();
|
||||
tx.send(chosen_word.to_string()).unwrap();
|
||||
vru_dialog_weak.unwrap().hide().unwrap();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user