aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Cargo.lock2395
-rw-r--r--Cargo.toml2
-rw-r--r--client/Cargo.toml48
-rw-r--r--client/src/client/helper.rs87
-rw-r--r--client/src/client/mod.rs463
-rw-r--r--client/src/lib.rs21
-rw-r--r--client/src/main.rs41
-rw-r--r--client/src/world/map.rs135
-rw-r--r--client/src/world/mod.rs132
-rw-r--r--renderer/Cargo.toml18
-rw-r--r--renderer/src/main.rs254
-rw-r--r--renderer/src/map.rs163
-rw-r--r--snapshot/Cargo.toml27
-rw-r--r--snapshot/src/format.rs85
-rw-r--r--snapshot/src/lib.rs36
-rw-r--r--snapshot/src/manager.rs140
-rw-r--r--snapshot/src/receiver.rs245
-rw-r--r--snapshot/src/snap.rs511
-rw-r--r--snapshot/src/storage.rs176
20 files changed, 4984 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..992fa97
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/target
+/client/maps
+/client/downloading
+/maps
+/downloading \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..538fd5e
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2395 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
+dependencies = [
+ "memchr 0.1.11",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr 2.4.1",
+]
+
+[[package]]
+name = "android_glue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bindgen"
+version = "0.59.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger 0.9.0",
+ "lazy_static",
+ "lazycell",
+ "log 0.4.17",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex 1.5.5",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "buffer"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aab7228d32b5d95be40adeba1d9461c8547b5dadf5f9cbfba09b6d578991df28"
+dependencies = [
+ "arrayvec",
+ "mac",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
+
+[[package]]
+name = "calloop"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82"
+dependencies = [
+ "log 0.4.17",
+ "nix",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cgl"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "chunked_transfer"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
+
+[[package]]
+name = "clang-sys"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim 0.8.0",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "core-foundation 0.9.3",
+ "core-graphics 0.22.3",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-foundation 0.9.3",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "common"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "file_offset",
+ "num-traits 0.1.43",
+ "ref_slice",
+ "unreachable",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+dependencies = [
+ "core-foundation-sys 0.7.0",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys 0.8.3",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "core-graphics"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.7.0",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.3",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.3",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-video-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828"
+dependencies = [
+ "cfg-if 0.1.10",
+ "core-foundation-sys 0.7.0",
+ "core-graphics 0.19.2",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "cty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "datafile"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "common",
+ "hexdump",
+ "itertools 0.4.19",
+ "log 0.3.9",
+ "logger",
+ "zlib_minimal",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dlib"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "env_logger"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
+dependencies = [
+ "log 0.3.9",
+ "regex 0.1.80",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log 0.4.17",
+ "regex 1.5.5",
+ "termcolor",
+]
+
+[[package]]
+name = "event_loop"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "common",
+ "hexdump",
+ "itertools 0.4.19",
+ "log 0.3.9",
+ "logger",
+ "net",
+ "socket",
+ "warn",
+]
+
+[[package]]
+name = "file_offset"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ad02ed760f100d78f087138613184d8e48d301c97f13804e250773d649900cd"
+
+[[package]]
+name = "filetime"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall 0.2.13",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "gamenet_common"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "packer",
+ "serde",
+ "serde_derive",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "gamenet_ddnet"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "gamenet_common",
+ "packer",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "gamenet_teeworlds_0_5"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "gamenet_common",
+ "packer",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "gamenet_teeworlds_0_6"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "gamenet_common",
+ "packer",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "gamenet_teeworlds_0_7"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "gamenet_common",
+ "packer",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gl"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log 0.4.17",
+ "xml-rs",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "glutin"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2"
+dependencies = [
+ "android_glue",
+ "cgl",
+ "cocoa",
+ "core-foundation 0.9.3",
+ "glutin_egl_sys",
+ "glutin_emscripten_sys",
+ "glutin_gles2_sys",
+ "glutin_glx_sys",
+ "glutin_wgl_sys",
+ "lazy_static",
+ "libloading",
+ "log 0.4.17",
+ "objc",
+ "osmesa-sys",
+ "parking_lot",
+ "wayland-client",
+ "wayland-egl",
+ "winapi 0.3.9",
+ "winit",
+]
+
+[[package]]
+name = "glutin_egl_sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211"
+dependencies = [
+ "gl_generator",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "glutin_emscripten_sys"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1"
+
+[[package]]
+name = "glutin_gles2_sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103"
+dependencies = [
+ "gl_generator",
+ "objc",
+]
+
+[[package]]
+name = "glutin_glx_sys"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351"
+dependencies = [
+ "gl_generator",
+ "x11-dl",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hexdump"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40283dadb02f3af778878be1d717b17b4e4ab92e1d935ab03a730b0542905f2"
+dependencies = [
+ "arrayvec",
+ "itertools 0.4.19",
+]
+
+[[package]]
+name = "huffman"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "itertools 0.4.19",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f"
+
+[[package]]
+name = "itertools"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "js-sys"
+version = "0.3.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.121"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linear-map"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee"
+
+[[package]]
+name = "lock_api"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+dependencies = [
+ "log 0.4.17",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "logger"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "env_logger 0.3.5",
+]
+
+[[package]]
+name = "mac"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1db08c0d0ddbb591e65f1da58d1cefccc94a2faa0c55bf979ce215a3e04d5e"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "map"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "common",
+ "datafile",
+ "ndarray",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002"
+dependencies = [
+ "rawpointer",
+]
+
+[[package]]
+name = "memchr"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "memmap2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log 0.4.17",
+ "miow",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
+dependencies = [
+ "libc",
+ "log 0.4.17",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "ndarray"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d0603e041601804a61a33ebecbea220b43a86437b9ee20680a1f9fdaaa922a8"
+dependencies = [
+ "itertools 0.6.5",
+ "matrixmultiply",
+ "num-complex",
+ "num-traits 0.1.43",
+]
+
+[[package]]
+name = "ndk"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d"
+dependencies = [
+ "bitflags",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-glue"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log 0.4.17",
+ "ndk",
+ "ndk-context",
+ "ndk-macro",
+ "ndk-sys",
+]
+
+[[package]]
+name = "ndk-macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
+dependencies = [
+ "darling",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ndk-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
+
+[[package]]
+name = "net"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "assert_matches",
+ "buffer",
+ "common",
+ "huffman",
+ "linear-map",
+ "matches",
+ "optional",
+ "void",
+ "warn",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "nix"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr 2.4.1",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
+dependencies = [
+ "num-traits 0.2.14",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
+dependencies = [
+ "num-traits 0.2.14",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
+[[package]]
+name = "optional"
+version = "0.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf72fc7f93afa4f42298f870abcc10ec1ab116198985719638bfe37535170092"
+
+[[package]]
+name = "osmesa-sys"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
+dependencies = [
+ "shared_library",
+]
+
+[[package]]
+name = "packer"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "arrayvec",
+ "buffer",
+ "common",
+ "uuid",
+ "warn",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.13",
+ "smallvec",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.3.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
+dependencies = [
+ "libc",
+ "rand 0.4.6",
+]
+
+[[package]]
+name = "rand"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41"
+dependencies = [
+ "cty",
+]
+
+[[package]]
+name = "rawpointer"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019"
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "ref_slice"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4ed1d73fb92eba9b841ba2aef69533a060ccc0d3ec71c90aeda5996d4afb7a9"
+
+[[package]]
+name = "regex"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
+dependencies = [
+ "aho-corasick 0.5.3",
+ "memchr 0.1.11",
+ "regex-syntax 0.3.9",
+ "thread_local",
+ "utf8-ranges",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+dependencies = [
+ "aho-corasick 0.7.18",
+ "memchr 2.4.1",
+ "regex-syntax 0.6.25",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustls"
+version = "0.20.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
+dependencies = [
+ "log 0.4.17",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.136"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.136"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shared_library"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
+dependencies = [
+ "lazy_static",
+ "libc",
+]
+
+[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "skia-bindings"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00f45439145d90b18bce3dc0bfb50c73b25a75666ac391089b23e4ee245a7f67"
+dependencies = [
+ "bindgen",
+ "cc",
+ "flate2",
+ "heck",
+ "lazy_static",
+ "regex 1.5.5",
+ "serde_json",
+ "tar",
+ "toml",
+ "ureq",
+]
+
+[[package]]
+name = "skia-safe"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5e8f14ca7a6e7338e7cf6bfdad314c4e735003c1a56001e1e545bd22c994d43"
+dependencies = [
+ "bitflags",
+ "lazy_static",
+ "skia-bindings",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
+
+[[package]]
+name = "smallvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3"
+dependencies = [
+ "bitflags",
+ "calloop",
+ "dlib",
+ "lazy_static",
+ "log 0.4.17",
+ "memmap2",
+ "nix",
+ "pkg-config",
+ "wayland-client",
+ "wayland-cursor",
+ "wayland-protocols",
+]
+
+[[package]]
+name = "snapshot"
+version = "0.0.1"
+dependencies = [
+ "buffer",
+ "common",
+ "gamenet_ddnet",
+ "gamenet_teeworlds_0_5",
+ "gamenet_teeworlds_0_6",
+ "gamenet_teeworlds_0_7",
+ "packer",
+ "vec_map",
+ "warn",
+]
+
+[[package]]
+name = "socket"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "buffer",
+ "common",
+ "hexdump",
+ "itertools 0.4.19",
+ "libc",
+ "log 0.3.9",
+ "mio 0.6.23",
+ "net",
+ "net2",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0"
+dependencies = [
+ "kernel32-sys",
+ "libc",
+ "rand 0.3.23",
+ "redox_syscall 0.1.57",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread-id"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
+dependencies = [
+ "kernel32-sys",
+ "libc",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
+dependencies = [
+ "thread-id",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "twclient"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "clap",
+ "common",
+ "crossbeam-channel",
+ "datafile",
+ "env_logger 0.9.0",
+ "event_loop",
+ "gamenet_ddnet",
+ "gamenet_teeworlds_0_5",
+ "gamenet_teeworlds_0_6",
+ "gamenet_teeworlds_0_7",
+ "hexdump",
+ "itertools 0.4.19",
+ "lazy_static",
+ "log 0.3.9",
+ "logger",
+ "map",
+ "ndarray",
+ "net",
+ "packer",
+ "rand 0.8.5",
+ "signal-hook",
+ "snapshot",
+ "socket",
+ "tempfile",
+ "warn",
+]
+
+[[package]]
+name = "twrenderer"
+version = "0.1.0"
+dependencies = [
+ "env_logger 0.9.0",
+ "gl",
+ "glutin",
+ "log 0.4.17",
+ "signal-hook",
+ "skia-safe",
+ "twclient",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+dependencies = [
+ "void",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "ureq"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5"
+dependencies = [
+ "base64",
+ "chunked_transfer",
+ "flate2",
+ "log 0.4.17",
+ "once_cell",
+ "rustls",
+ "url",
+ "webpki",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "warn"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2638072850e7fde2e4e96d78204024000382e5666876bd8a3e7e9db6ed22cb"
+dependencies = [
+ "log 0.3.9",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log 0.4.17",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
+
+[[package]]
+name = "wayland-client"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f"
+dependencies = [
+ "bitflags",
+ "downcast-rs",
+ "libc",
+ "nix",
+ "scoped-tls",
+ "wayland-commons",
+ "wayland-scanner",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-commons"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e"
+dependencies = [
+ "nix",
+ "once_cell",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd"
+dependencies = [
+ "nix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-egl"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a"
+dependencies = [
+ "wayland-client",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741"
+dependencies = [
+ "bitflags",
+ "wayland-client",
+ "wayland-commons",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "xml-rs",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.29.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4"
+dependencies = [
+ "dlib",
+ "lazy_static",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "which"
+version = "4.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
+dependencies = [
+ "either",
+ "lazy_static",
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "winit"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a"
+dependencies = [
+ "bitflags",
+ "cocoa",
+ "core-foundation 0.9.3",
+ "core-graphics 0.22.3",
+ "core-video-sys",
+ "dispatch",
+ "instant",
+ "lazy_static",
+ "libc",
+ "log 0.4.17",
+ "mio 0.8.3",
+ "ndk",
+ "ndk-glue",
+ "ndk-sys",
+ "objc",
+ "parking_lot",
+ "percent-encoding",
+ "raw-window-handle",
+ "smithay-client-toolkit",
+ "wasm-bindgen",
+ "wayland-client",
+ "wayland-protocols",
+ "web-sys",
+ "winapi 0.3.9",
+ "x11-dl",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "xcursor"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "xml-rs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+
+[[package]]
+name = "zlib_minimal"
+version = "0.0.1"
+source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3"
+dependencies = [
+ "libc",
+ "libz-sys",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..19cae09
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["client","snapshot","renderer"] \ No newline at end of file
diff --git a/client/Cargo.toml b/client/Cargo.toml
new file mode 100644
index 0000000..5f14df4
--- /dev/null
+++ b/client/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "twclient"
+version = "0.1.0"
+edition = "2021"
+authors = [
+ "metamuffin <metamuffin@disroot.org>",
+ "heinrich5991 <heinrich5991@gmail.com>",
+]
+license = "AGPL-3.0-only"
+
+[dependencies]
+socket = { git = "https://github.com/heinrich5991/libtw2" }
+datafile = { git = "https://github.com/heinrich5991/libtw2" }
+map = { git = "https://github.com/heinrich5991/libtw2" }
+net = { git = "https://github.com/heinrich5991/libtw2" }
+common = { git = "https://github.com/heinrich5991/libtw2" }
+event_loop = { git = "https://github.com/heinrich5991/libtw2" }
+gamenet_teeworlds_0_5 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_teeworlds_0_6 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_teeworlds_0_7 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_ddnet = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+logger = { git = "https://github.com/heinrich5991/libtw2" }
+packer = { git = "https://github.com/heinrich5991/libtw2" }
+# snapshot = { git = "https://github.com/heinrich5991/libtw2" }
+snapshot = { path = "../snapshot" }
+
+ndarray = "0.9.1"
+arrayvec = "0.5.2"
+clap = "2.31.2"
+hexdump = "0.1.1"
+itertools = ">=0.3.0,<0.5.0"
+log = "0.3.1"
+rand = "0.8.3"
+tempfile = "2.0.0"
+warn = ">=0.1.1,<0.3.0"
+env_logger = "0.9.0"
+signal-hook = "0.3.14"
+lazy_static = "1.4.0"
+anyhow = "1.0.57"
+crossbeam-channel = "0.5.4"
+
+
+[features]
+default = ["gamenet_ddnet_0_6"]
+gamenet_0_5 = ["dep:gamenet_teeworlds_0_5", "snapshot/gamenet_teeworlds_0_5"]
+gamenet_0_6 = ["dep:gamenet_teeworlds_0_6", "snapshot/gamenet_teeworlds_0_6"]
+gamenet_0_7 = ["dep:gamenet_teeworlds_0_7", "snapshot/gamenet_teeworlds_0_7"]
+gamenet_ddnet_0_6 = ["dep:gamenet_ddnet", "snapshot/gamenet_ddnet_0_6"]
diff --git a/client/src/client/helper.rs b/client/src/client/helper.rs
new file mode 100644
index 0000000..af19301
--- /dev/null
+++ b/client/src/client/helper.rs
@@ -0,0 +1,87 @@
+use arrayvec::ArrayVec;
+use event_loop::{Chunk, Loop, PeerId};
+use gamenet::msg::{Game, System};
+use hexdump::hexdump_iter;
+use itertools::Itertools;
+use log::LogLevel;
+use log::{log, log_enabled, warn};
+use packer::with_packer;
+use snapshot::format::Item as SnapItem;
+use std::{fmt, path::PathBuf};
+
+pub trait LoopExt: Loop {
+ fn send_system<'a, S: Into<System<'a>>>(&mut self, pid: PeerId, msg: S) {
+ fn inner<L: Loop + ?Sized>(msg: System, pid: PeerId, evloop: &mut L) {
+ let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new();
+ with_packer(&mut buf, |p| msg.encode(p).unwrap());
+ evloop.send(Chunk {
+ pid,
+ vital: true,
+ data: &buf,
+ })
+ }
+ inner(msg.into(), pid, self)
+ }
+ fn send_game<'a, G: Into<Game<'a>>>(&mut self, pid: PeerId, msg: G) {
+ fn inner<L: Loop + ?Sized>(msg: Game, pid: PeerId, evloop: &mut L) {
+ let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new();
+ with_packer(&mut buf, |p| msg.encode(p).unwrap());
+ evloop.send(Chunk {
+ pid,
+ vital: true,
+ data: &buf,
+ })
+ }
+ inner(msg.into(), pid, self)
+ }
+}
+impl<L: Loop> LoopExt for L {}
+
+pub fn need_file(crc: i32, name: &str) -> bool {
+ let mut path = PathBuf::new();
+ path.push("/tmp/maps");
+ path.push(format!("{}_{:08x}.map", name, crc));
+ !path.exists()
+}
+
+pub fn hexdump(level: LogLevel, data: &[u8]) {
+ if log_enabled!(level) {
+ hexdump_iter(data).foreach(|s| log!(level, "{}", s));
+ }
+}
+pub struct Warn<'a>(pub &'a [u8]);
+
+impl<'a, W: fmt::Debug> warn::Warn<W> for Warn<'a> {
+ fn warn(&mut self, w: W) {
+ warn!("{:?}", w);
+ hexdump(LogLevel::Warn, self.0);
+ }
+}
+
+#[derive(Debug)]
+pub struct WarnSnap<'a>(pub SnapItem<'a>);
+
+impl<'a, W: fmt::Debug> warn::Warn<W> for WarnSnap<'a> {
+ fn warn(&mut self, w: W) {
+ warn!("{:?} for {:?}", w, self.0);
+ }
+}
+
+pub fn check_dummy_map(name: &[u8], crc: u32, size: i32) -> bool {
+ if name != b"dummy" {
+ return false;
+ }
+ match (crc, size) {
+ (0xbeae0b9f, 549) => {}
+ (0x6c760ac4, 306) => {}
+ _ => warn!("unknown dummy map, crc={}, size={}", crc, size),
+ }
+ true
+}
+
+pub fn get_map_path(name: &str, crc: i32) -> PathBuf {
+ let mut path = PathBuf::new();
+ path.push("/tmp/maps");
+ path.push(format!("{}_{:08x}.map", name, crc));
+ path
+}
diff --git a/client/src/client/mod.rs b/client/src/client/mod.rs
new file mode 100644
index 0000000..c72cc0b
--- /dev/null
+++ b/client/src/client/mod.rs
@@ -0,0 +1,463 @@
+pub mod helper;
+
+use self::helper::get_map_path;
+
+use super::gamenet::{
+ enums::{Team, VERSION},
+ msg::{
+ self,
+ game::{ClSetTeam, ClStartInfo},
+ system::{EnterGame, Info, Input, MapChange, MapData},
+ system::{Ready, RequestMapData},
+ Game, System, SystemOrGame,
+ },
+ snap_obj::{obj_size, PlayerInput},
+ SnapObj,
+};
+use crate::client::helper::{check_dummy_map, hexdump, WarnSnap};
+use crate::{
+ client::helper::{need_file, LoopExt, Warn},
+ SHOULD_EXIT,
+};
+use common::{num::Cast, pretty};
+use crossbeam_channel::{Receiver, Sender};
+use event_loop::{Addr, Application, Chunk, ConnlessChunk, Loop, PeerId, SocketLoop, Timeout};
+use log::LogLevel;
+use log::{debug, error, info, log, warn};
+use packer::{IntUnpacker, Unpacker};
+use std::{
+ borrow::{Borrow, Cow},
+ collections::HashSet,
+ fs,
+ io::{self, Write},
+ mem,
+ net::IpAddr,
+ sync::atomic::Ordering,
+ u32,
+};
+use tempfile::{NamedTempFile, NamedTempFileOptions};
+use warn::Log;
+
+pub struct ClientConfig {
+ pub nick: String,
+ pub clan: String,
+ pub timeout: String,
+}
+
+pub struct Client {
+ pid: PeerId,
+ config: ClientConfig,
+
+ current_votes: HashSet<Vec<u8>>,
+ snaps: snapshot::Manager,
+ num_snaps_since_reset: u64,
+ dummy_map: bool,
+ state: ClientState,
+ download: Option<Download>,
+
+ _interface_receive: Receiver<ClientMesgIn>,
+ interface_send: Sender<ClientMesgOut>,
+}
+
+pub struct ClientInterface {
+ pub send: Sender<ClientMesgIn>,
+ pub receive: Receiver<ClientMesgOut>,
+}
+
+pub enum ClientMesgIn {}
+pub enum ClientMesgOut {
+ MapChange { name: String, crc: i32 },
+ Snaps(Vec<(u16, SnapObj)>),
+}
+
+#[derive(Clone, Copy, Debug)]
+enum ClientState {
+ Connection,
+ MapChange,
+ MapData(i32, i32),
+ ConReady,
+ ReadyToEnter,
+}
+
+impl Default for ClientState {
+ fn default() -> ClientState {
+ ClientState::Connection
+ }
+}
+
+impl Client {
+ pub fn new_evloop() -> SocketLoop {
+ SocketLoop::client()
+ }
+
+ pub fn new(
+ evloop: &mut SocketLoop,
+ ip: IpAddr,
+ port: u16,
+ config: ClientConfig,
+ ) -> (Self, ClientInterface) {
+ fs::create_dir_all("/tmp/maps").unwrap();
+ fs::create_dir_all("/tmp/downloading").unwrap();
+ let (a, b) = crossbeam_channel::unbounded();
+ let (c, d) = crossbeam_channel::unbounded();
+ (
+ Client {
+ pid: evloop.connect(Addr { ip, port }),
+ config,
+ current_votes: HashSet::new(),
+ snaps: snapshot::Manager::new(),
+ num_snaps_since_reset: 0,
+ dummy_map: false,
+ state: ClientState::Connection,
+ download: None,
+ _interface_receive: b,
+ interface_send: c,
+ },
+ ClientInterface {
+ receive: d,
+ send: a,
+ },
+ )
+ }
+ pub fn run(self, evloop: SocketLoop) {
+ evloop.run(self)
+ }
+}
+
+impl<'a, L: Loop> Application<L> for Client {
+ fn needs_tick(&mut self) -> Timeout {
+ Timeout::inactive()
+ }
+ fn on_tick(&mut self, evloop: &mut L) {
+ if SHOULD_EXIT.load(Ordering::Relaxed) {
+ warn!("exiting peer {}", self.pid);
+ evloop.disconnect(self.pid, b"error");
+ }
+ }
+ fn on_packet(&mut self, evloop: &mut L, chunk: Chunk) {
+ let pid = chunk.pid;
+
+ let msg = match msg::decode(&mut Warn(chunk.data), &mut Unpacker::new(chunk.data)) {
+ Ok(m) => m,
+ Err(err) => {
+ warn!("decode error {:?}:", err);
+ hexdump(LogLevel::Warn, chunk.data);
+ return;
+ }
+ };
+ debug!("{:?}", msg);
+ match msg {
+ SystemOrGame::Game(Game::SvMotd(..))
+ | SystemOrGame::Game(Game::SvKillMsg(..))
+ | SystemOrGame::Game(Game::SvTuneParams(..))
+ | SystemOrGame::Game(Game::SvWeaponPickup(..))
+ | SystemOrGame::System(System::InputTiming(..))
+ | SystemOrGame::Game(Game::SvExtraProjectile(..)) => {}
+ SystemOrGame::Game(Game::SvChat(chat)) => {
+ if chat.client_id == -1 {
+ info!("[server]: {}", pretty::AlmostString::new(chat.message));
+ } else {
+ info!(
+ "[team {}]: {}",
+ chat.team,
+ pretty::AlmostString::new(chat.message)
+ )
+ }
+ }
+ SystemOrGame::Game(Game::SvBroadcast(broadcast)) => {
+ info!(
+ "broadcast: {}",
+ pretty::AlmostString::new(broadcast.message)
+ );
+ }
+ _ => {}
+ }
+
+ match msg {
+ SystemOrGame::System(ref msg) => match *msg {
+ System::MapChange(MapChange { crc, size, name }) => {
+ if let Some(_) = size.try_usize() {
+ if name.iter().any(|&b| b == b'/' || b == b'\\') {
+ error!("invalid map name");
+ evloop.disconnect(pid, b"error");
+ return;
+ }
+ match self.state {
+ ClientState::MapChange => {}
+ ClientState::ReadyToEnter if self.dummy_map => {}
+ _ => warn!("map change from state {:?}", self.state),
+ }
+ self.dummy_map = check_dummy_map(name, crc as u32, size);
+ self.current_votes.clear();
+ self.num_snaps_since_reset = 0;
+ self.snaps.reset();
+ info!("map change: {}", pretty::AlmostString::new(name));
+ let name = String::from_utf8_lossy(name);
+ if let Cow::Owned(..) = name {
+ warn!("weird characters in map name");
+ }
+ let mut start_download = false;
+ if need_file(crc, &name) {
+ if let Err(e) = self.open_download_file(crc, name.borrow()) {
+ error!("error opening file {:?}", e);
+ } else {
+ start_download = true;
+ }
+ }
+ if start_download {
+ info!("download starting");
+ evloop.send_system(pid, RequestMapData { chunk: 0 });
+ self.state = ClientState::MapData(crc, 0);
+ } else {
+ self.state = ClientState::ConReady;
+ evloop.send_system(pid, Ready);
+
+ self.interface_send
+ .send(ClientMesgOut::MapChange {
+ name: name.to_string(),
+ crc,
+ })
+ .unwrap();
+ }
+ } else {
+ error!("invalid map size");
+ evloop.disconnect(pid, b"error");
+ return;
+ }
+ }
+ System::Snap(_) | System::SnapEmpty(_) | System::SnapSingle(_) => {
+ self.num_snaps_since_reset += 1;
+ let mut input = PlayerInput::default();
+ {
+ let res = match *msg {
+ System::Snap(s) => self.snaps.snap(&mut Log, obj_size, s),
+ System::SnapEmpty(s) => self.snaps.snap_empty(&mut Log, obj_size, s),
+ System::SnapSingle(s) => self.snaps.snap_single(&mut Log, obj_size, s),
+ _ => unreachable!(),
+ };
+ match res {
+ Ok(Some(snap)) => {
+ let snaps = snap
+ .items()
+ .filter_map(|item| {
+ SnapObj::decode_obj(
+ &mut WarnSnap(item),
+ item.type_id.into(),
+ &mut IntUnpacker::new(item.data),
+ )
+ .map(|o| (item.id, o))
+ .ok()
+ })
+ .collect::<Vec<(u16, SnapObj)>>();
+
+ self.interface_send
+ .send(ClientMesgOut::Snaps(snaps.clone()))
+ .unwrap();
+
+ let client_id = snaps
+ .iter()
+ .filter_map(|(_id, i)| {
+ if let SnapObj::PlayerInfo(p) = i {
+ if p.local != 0 {
+ Some(p.client_id)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+ .next()
+ .unwrap_or(1 << 15)
+ as u16;
+
+ let mut target_position = None;
+ let mut my_position = (0, 0);
+ for (id, snap) in &snaps {
+ match snap {
+ SnapObj::Character(c) => {
+ let c = c.character_core;
+ if *id == client_id {
+ my_position = (c.x, c.y)
+ } else if c.hook_state != 0 {
+ target_position = Some((c.x, c.y))
+ }
+ }
+ _ => {}
+ }
+ }
+ if let Some(target) = target_position {
+ if target.0 < my_position.0 {
+ input.direction = -1;
+ }
+ if target.0 > my_position.0 {
+ input.direction = 1;
+ }
+ if target.1 - 16 < my_position.1 {
+ input.jump = 1;
+ }
+ }
+ }
+ Ok(None) => {
+ self.num_snaps_since_reset -= 1;
+ }
+ Err(err) => warn!("snapshot error {:?}", err),
+ }
+ }
+ // DDNet needs the INPUT message as the first
+ // chunk of the packet.
+ evloop.force_flush(pid);
+ let tick = self.snaps.ack_tick().unwrap_or(-1);
+ evloop.send_system(
+ pid,
+ Input {
+ ack_snapshot: tick,
+ intended_tick: tick,
+ input_size: mem::size_of::<PlayerInput>().assert_i32(),
+ input,
+ },
+ );
+ }
+ _ => {}
+ },
+ SystemOrGame::Game(ref msg) => match *msg {
+ _ => {}
+ },
+ }
+ match self.state {
+ ClientState::Connection => unreachable!(),
+ ClientState::MapChange => {} // Handled above.
+ ClientState::MapData(cur_crc, cur_chunk) => match msg {
+ SystemOrGame::System(System::MapData(MapData {
+ last,
+ crc,
+ chunk,
+ data,
+ })) => {
+ if cur_crc == crc && cur_chunk == chunk {
+ let res = self.write_download_file(data);
+ if let Err(ref err) = res {
+ error!("error writing file {:?}", err);
+ }
+ if last != 0 || res.is_err() {
+ if !res.is_err() {
+ if let Err(err) = self.finish_download_file() {
+ error!("error finishing file {:?}", err);
+ }
+ if last != 1 {
+ warn!("weird map data packet");
+ }
+ }
+ self.state = ClientState::ConReady;
+ evloop.send_system(pid, Ready);
+
+ info!("download finished");
+ } else {
+ let cur_chunk = cur_chunk.checked_add(1).unwrap();
+ self.state = ClientState::MapData(cur_crc, cur_chunk);
+ evloop.send_system(pid, RequestMapData { chunk: cur_chunk });
+ }
+ } else {
+ if cur_crc != crc || cur_chunk < chunk {
+ warn!("unsolicited map data crc={:08x} chunk={}", crc, chunk);
+ warn!("want crc={:08x} chunk={}", cur_crc, cur_chunk);
+ }
+ }
+ }
+ _ => {}
+ },
+ ClientState::ConReady => match msg {
+ SystemOrGame::System(System::ConReady(..)) => {
+ evloop.send_game(
+ pid,
+ ClStartInfo {
+ name: self.config.nick.as_bytes(),
+ clan: self.config.clan.as_bytes(),
+ country: -1,
+ skin: b"limekittygirl",
+ use_custom_color: true,
+ color_body: 0xFF00FF,
+ color_feet: 0x550055,
+ },
+ );
+ self.state = ClientState::ReadyToEnter;
+ }
+ _ => {}
+ },
+ ClientState::ReadyToEnter => match msg {
+ SystemOrGame::Game(Game::SvReadyToEnter(..)) => {
+ evloop.send_system(pid, EnterGame);
+ evloop.send_game(pid, ClSetTeam { team: Team::Red });
+ }
+ _ => {}
+ },
+ }
+ evloop.flush(pid);
+ }
+ fn on_connless_packet(&mut self, _: &mut L, chunk: ConnlessChunk) {
+ warn!(
+ "connless packet {} {:?}",
+ chunk.addr,
+ pretty::Bytes::new(chunk.data)
+ );
+ }
+ fn on_connect(&mut self, _: &mut L, _: PeerId) {
+ unreachable!();
+ }
+ fn on_ready(&mut self, evloop: &mut L, pid: PeerId) {
+ if pid != self.pid {
+ error!("not our pid: {} vs {}", pid, self.pid);
+ }
+ self.state = ClientState::MapChange;
+ evloop.send_system(
+ pid,
+ Info {
+ version: VERSION.as_bytes(),
+ password: Some(b""),
+ },
+ );
+ evloop.flush(pid);
+ }
+ fn on_disconnect(&mut self, _: &mut L, pid: PeerId, remote: bool, reason: &[u8]) {
+ if remote {
+ error!(
+ "disconnected pid={:?} error={}",
+ pid,
+ pretty::AlmostString::new(reason)
+ );
+ }
+ }
+}
+
+struct Download {
+ file: NamedTempFile,
+ crc: i32,
+ name: String,
+}
+
+impl Client {
+ fn open_download_file(&mut self, crc: i32, name: &str) -> Result<(), io::Error> {
+ self.download = Some(Download {
+ file: NamedTempFileOptions::new()
+ .prefix(&format!("{}_{:08x}_", name, crc))
+ .suffix(".map")
+ .create_in("/tmp/downloading")?,
+ crc,
+ name: name.to_string(),
+ });
+ Ok(())
+ }
+ fn write_download_file(&mut self, data: &[u8]) -> Result<(), io::Error> {
+ self.download.as_mut().unwrap().file.write_all(data)
+ }
+ fn finish_download_file(&mut self) -> Result<(), io::Error> {
+ let download = self.download.take().unwrap();
+ let path = get_map_path(download.name.as_str(), download.crc);
+ download
+ .file
+ .persist(&path)
+ .map(|_| ())
+ .map_err(|e| e.error)?;
+ Ok(())
+ }
+}
diff --git a/client/src/lib.rs b/client/src/lib.rs
new file mode 100644
index 0000000..3c19df7
--- /dev/null
+++ b/client/src/lib.rs
@@ -0,0 +1,21 @@
+#![feature(exclusive_range_pattern)]
+
+pub mod client;
+pub mod world;
+
+#[cfg(feature = "gamenet_ddnet_0_6")]
+pub extern crate gamenet_ddnet as gamenet;
+#[cfg(feature = "gamenet_0_5")]
+pub extern crate gamenet_teeworlds_0_5 as gamenet;
+#[cfg(feature = "gamenet_0_6")]
+pub extern crate gamenet_teeworlds_0_6 as gamenet;
+#[cfg(feature = "gamenet_0_7")]
+pub extern crate gamenet_teeworlds_0_7 as gamenet;
+
+
+use std::sync::atomic::AtomicBool;
+
+use lazy_static::lazy_static;
+lazy_static! {
+ pub static ref SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
+}
diff --git a/client/src/main.rs b/client/src/main.rs
new file mode 100644
index 0000000..b79f338
--- /dev/null
+++ b/client/src/main.rs
@@ -0,0 +1,41 @@
+use log::{error, log, warn};
+use signal_hook::{
+ consts::{SIGINT, SIGTERM},
+ iterator::Signals,
+ low_level::exit,
+};
+use std::{net::IpAddr, str::FromStr, sync::atomic::Ordering, thread, time::Duration};
+use twclient::{
+ client::{Client, ClientConfig},
+ SHOULD_EXIT,
+};
+
+fn main() {
+ env_logger::init();
+
+ let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
+ thread::spawn(move || {
+ for sig in signals.forever() {
+ warn!("received signal {:?}", sig);
+ SHOULD_EXIT.store(true, Ordering::Relaxed);
+ thread::sleep(Duration::from_secs(3));
+ error!("exit timeout!");
+ exit(1);
+ }
+ });
+
+ let config = ClientConfig {
+ nick: String::from("metamuffin"),
+ clan: String::from("rustacean"),
+ timeout: String::from("asgefdhjikhjfhjf"),
+ };
+ let mut args = std::env::args().skip(1);
+ let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap();
+ let port = u16::from_str(args.next().unwrap().as_str()).unwrap();
+ drop(ip);
+ drop(port);
+ drop(config);
+ drop(Client::new_evloop());
+ todo!()
+ // Client::run_thing(ip, port, config)
+}
diff --git a/client/src/world/map.rs b/client/src/world/map.rs
new file mode 100644
index 0000000..fb67918
--- /dev/null
+++ b/client/src/world/map.rs
@@ -0,0 +1,135 @@
+use ::map as mapfile;
+use anyhow::Error;
+use common::{num::Cast, vec};
+use log::{info, log};
+use ndarray::Array2;
+use std::{
+ collections::{hash_map, HashMap},
+ fs::File,
+ mem,
+};
+
+pub use mapfile::format;
+pub use mapfile::{
+ format::Tile,
+ reader::{self, Color, LayerTilemapType},
+};
+pub const TILE_NUM: u32 = 16;
+
+pub struct Layer {
+ pub color: Color,
+ pub image: Option<usize>,
+ pub tiles: Array2<Tile>,
+ pub kind: LayerTilemapType,
+}
+
+pub struct Map {
+ pub layers: Vec<Layer>,
+ pub tilesets: HashMap<Option<usize>, Array2<Color>>,
+}
+
+impl Map {
+ pub fn empty() -> Self {
+ Self {
+ layers: Vec::new(),
+ tilesets: HashMap::new(),
+ }
+ }
+
+ pub fn load(file: File) -> Result<Self, Error> {
+ info!("loading map");
+ let datafile = datafile::Reader::new(file).unwrap();
+ let mut map = mapfile::Reader::from_datafile(datafile);
+
+ let mut layers = vec![];
+ for group_idx in map.group_indices() {
+ let group = map.group(group_idx).unwrap();
+
+ if group.parallax_x != 100
+ || group.parallax_y != 100
+ || group.offset_x != 0
+ || group.offset_y != 0
+ || group.clipping.is_some()
+ {
+ continue;
+ }
+
+ for layer_idx in group.layer_indices {
+ let layer = map.layer(layer_idx).unwrap();
+
+ let tilemap = if let reader::LayerType::Tilemap(t) = layer.t {
+ t
+ } else {
+ continue;
+ };
+ let normal = if let Some(n) = tilemap.type_.to_normal() {
+ n
+ } else {
+ continue;
+ };
+ let tiles = map.layer_tiles(tilemap.tiles(normal.data)).unwrap();
+ layers.push(Layer {
+ color: normal.color,
+ image: normal.image,
+ kind: tilemap.type_,
+ tiles,
+ });
+ }
+ }
+
+ let mut tilesets = HashMap::new();
+ for layer in &layers {
+ match tilesets.entry(layer.image) {
+ hash_map::Entry::Occupied(_) => {}
+ hash_map::Entry::Vacant(v) => {
+ let data = match layer.image {
+ None => Array2::from_elem(
+ (1, 1),
+ Color {
+ alpha: 255,
+ red: 255,
+ blue: 255,
+ green: 0,
+ },
+ ),
+ Some(image_idx) => {
+ let image = map.image(image_idx).unwrap();
+ let height = image.height.usize();
+ let width = image.width.usize();
+ match image.data {
+ Some(d) => {
+ let data = map.image_data(d).unwrap();
+ if data.len() % mem::size_of::<Color>() != 0 {
+ panic!("image shape invalid");
+ }
+ let data: Vec<Color> = unsafe { vec::transmute(data) };
+ Array2::from_shape_vec((height, width), data).unwrap()
+ }
+ None => {
+ continue;
+ // let image_name = map.image_name(image.name)?;
+ // // WARN? Unknown external image
+ // // WARN! Wrong dimensions
+ // str::from_utf8(&image_name).ok()
+ // .and_then(sanitize)
+ // .map(&mut external_tileset_loader)
+ // .transpose()?
+ // .unwrap_or(None)
+ // .unwrap_or_else(|| Array2::from_elem((1, 1), Color::white()))
+ }
+ }
+ }
+ };
+ v.insert(data);
+ }
+ }
+ }
+
+ info!(
+ "{} layers + {} tilesets loaded",
+ layers.len(),
+ tilesets.len()
+ );
+ Ok(Self { tilesets, layers })
+ }
+}
diff --git a/client/src/world/mod.rs b/client/src/world/mod.rs
new file mode 100644
index 0000000..ae985a6
--- /dev/null
+++ b/client/src/world/mod.rs
@@ -0,0 +1,132 @@
+use gamenet::{
+ enums::{Emote, Team, Weapon},
+ SnapObj,
+};
+
+use self::map::Map;
+use crate::client::{helper::get_map_path, ClientMesgOut};
+use std::{collections::BTreeMap, fs::File};
+
+pub mod map;
+
+pub use gamenet::enums;
+
+#[derive(Debug)]
+pub struct Tee {
+ pub local: bool,
+ pub latency: i32,
+ pub score: i32,
+
+ pub team: Team,
+ pub weapon: Weapon,
+ pub armor: i32,
+ pub ammo: i32,
+ pub emote: Emote,
+ pub attack_tick: i32,
+
+ pub tick: i32,
+ pub angle: i32,
+ pub x: i32,
+ pub y: i32,
+ pub vel_x: i32,
+ pub vel_y: i32,
+ pub hook_x: i32,
+ pub hook_y: i32,
+ pub hook_dx: i32,
+ pub hook_dy: i32,
+ pub hook_player: i32,
+ pub hook_state: i32,
+}
+
+impl Default for Tee {
+ fn default() -> Self {
+ Self {
+ x: Default::default(),
+ y: Default::default(),
+ local: false,
+ team: Team::Spectators,
+ latency: Default::default(),
+ score: Default::default(),
+ weapon: Weapon::Shotgun,
+ armor: Default::default(),
+ ammo: Default::default(),
+ attack_tick: Default::default(),
+ emote: Emote::Normal,
+ tick: Default::default(),
+ angle: Default::default(),
+ vel_x: Default::default(),
+ vel_y: Default::default(),
+ hook_x: Default::default(),
+ hook_y: Default::default(),
+ hook_dx: Default::default(),
+ hook_dy: Default::default(),
+ hook_player: Default::default(),
+ hook_state: Default::default(),
+ }
+ }
+}
+
+pub struct World {
+ pub map: Map,
+ pub tees: BTreeMap<u16, Tee>,
+}
+
+impl World {
+ pub fn new() -> Self {
+ Self {
+ map: Map::empty(),
+ tees: BTreeMap::new(),
+ }
+ }
+
+ pub fn update(&mut self, m: &ClientMesgOut) {
+ match m {
+ ClientMesgOut::MapChange { name, crc } => {
+ let file = File::open(get_map_path(name.as_str(), *crc)).unwrap();
+ self.map = Map::load(file).unwrap();
+ }
+ ClientMesgOut::Snaps(s) => {
+ self.tees.clear();
+ for (id, o) in s {
+ match o {
+ SnapObj::ClientInfo(_o) => {
+ // TODO
+ }
+ SnapObj::PlayerInfo(o) => {
+ let e = self.tees.entry(*id).or_default();
+ e.local = o.local == 1;
+ e.team = o.team;
+ e.latency = o.latency;
+ e.score = o.score;
+ }
+ SnapObj::Character(c) => {
+ let e = self.tees.entry(*id).or_default();
+ e.ammo = c.ammo_count;
+ e.weapon = c.weapon;
+ e.emote = c.emote;
+ e.attack_tick = c.attack_tick;
+
+ e.x = c.character_core.x;
+ e.y = c.character_core.y;
+ e.vel_x = c.character_core.vel_x;
+ e.vel_y = c.character_core.vel_y;
+
+ e.tick = c.character_core.tick;
+ e.hook_x = c.character_core.hook_x;
+ e.hook_y = c.character_core.hook_y;
+ e.hook_player = c.character_core.hooked_player;
+ e.hook_dx = c.character_core.hook_dx;
+ e.hook_dy = c.character_core.hook_dy;
+ e.hook_state = c.character_core.hook_state;
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+ }
+
+ pub fn local_tee(&self) -> Option<&Tee> {
+ self.tees.values().find(|e| e.local)
+ }
+}
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
new file mode 100644
index 0000000..6ec23ca
--- /dev/null
+++ b/renderer/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "twrenderer"
+version = "0.1.0"
+edition = "2021"
+authors = [
+ "metamuffin <metamuffin@disroot.org>",
+ "heinrich5991 <heinrich5991@gmail.com>",
+]
+license = "AGPL-3.0-only"
+
+[dependencies]
+twclient = { path = "../client" }
+skia-safe = { version = "0.50.0", features = ["gl", "wayland"] }
+glutin = "0.28.0"
+gl = "0.14.0"
+log = "0.4.17"
+env_logger = "0.9.0"
+signal-hook = "0.3.14"
diff --git a/renderer/src/main.rs b/renderer/src/main.rs
new file mode 100644
index 0000000..d763336
--- /dev/null
+++ b/renderer/src/main.rs
@@ -0,0 +1,254 @@
+pub mod map;
+
+use glutin::{
+ event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
+ window::{Window, WindowBuilder},
+ ContextWrapper, GlProfile, PossiblyCurrent,
+};
+use log::{error, info, warn};
+use map::MapRenderer;
+use signal_hook::{
+ consts::{SIGINT, SIGTERM},
+ iterator::Signals,
+};
+use skia_safe::{
+ gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin},
+ Canvas, Color, Color4f, ColorSpace, ColorType, Paint, Point, Surface,
+};
+use std::{
+ convert::TryInto, net::IpAddr, process::exit, str::FromStr, sync::atomic::Ordering, thread,
+ time::Duration,
+};
+use twclient::{
+ client::{Client, ClientConfig, ClientInterface},
+ world::World,
+ SHOULD_EXIT,
+};
+
+fn main() {
+ env_logger::init();
+
+ let event_loop = EventLoop::new();
+ let wb = WindowBuilder::new().with_title("teeworlds");
+
+ let cb = glutin::ContextBuilder::new()
+ .with_depth_buffer(0)
+ .with_stencil_buffer(8)
+ .with_pixel_format(24, 8)
+ .with_gl_profile(GlProfile::Core);
+
+ // TODO
+ // #[cfg(not(feature = "wayland"))]
+ // let cb = cb.with_double_buffer(Some(true));
+
+ let windowed_context = cb.build_windowed(wb, &event_loop).unwrap();
+
+ let windowed_context = unsafe { windowed_context.make_current().unwrap() };
+
+ gl::load_with(|s| windowed_context.get_proc_address(s));
+
+ let mut gr_context = skia_safe::gpu::DirectContext::new_gl(None, None).unwrap();
+
+ let fb_info = {
+ use gl::types::GLint;
+ let mut fboid: GLint = 0;
+ unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
+
+ FramebufferInfo {
+ fboid: fboid.try_into().unwrap(),
+ format: skia_safe::gpu::gl::Format::RGBA8.into(),
+ }
+ };
+
+ windowed_context
+ .window()
+ .set_inner_size(glutin::dpi::Size::new(glutin::dpi::LogicalSize::new(
+ 1024.0, 1024.0,
+ )));
+
+ let surface = create_surface(&windowed_context, &fb_info, &mut gr_context);
+
+ struct Env {
+ surface: Surface,
+ gr_context: skia_safe::gpu::DirectContext,
+ windowed_context: ContextWrapper<PossiblyCurrent, Window>,
+ }
+
+ let mut env = Env {
+ surface,
+ gr_context,
+ windowed_context,
+ };
+
+ let mut args = std::env::args().skip(1);
+ let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap();
+ let port = u16::from_str(args.next().unwrap().as_str()).unwrap();
+ let mut evloop = Client::new_evloop();
+ let (client, client_interface) = Client::new(
+ &mut evloop,
+ ip,
+ port,
+ ClientConfig {
+ nick: "metamuffin".to_string(),
+ clan: "rustacean".to_string(),
+ timeout: "sdfhaiusdfhus".to_string(),
+ },
+ );
+ let mut network_thread = Some(std::thread::spawn(move || client.run(evloop)));
+
+ let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
+ info!("setting up signal handlers");
+ thread::spawn(move || {
+ for sig in signals.forever() {
+ warn!("received signal {:?}", sig);
+ SHOULD_EXIT.store(true, Ordering::Relaxed);
+ thread::sleep(Duration::from_secs(3));
+ error!("exit timeout!");
+ exit(1);
+ }
+ });
+
+ let mut renderer = Renderer {
+ client_interface,
+ map_renderer: MapRenderer::new(),
+ world: World::new(),
+ };
+
+ event_loop.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Wait;
+
+ #[allow(deprecated)]
+ match event {
+ Event::LoopDestroyed => {}
+ Event::RedrawEventsCleared => {
+ if SHOULD_EXIT.load(Ordering::Relaxed) {
+ warn!("waiting for networt thread to finish");
+ network_thread.take().unwrap().join().unwrap();
+ warn!("exiting renderer");
+ exit(0);
+ }
+ renderer.tick();
+ env.windowed_context.window().request_redraw();
+ }
+ Event::WindowEvent { event, .. } => match event {
+ WindowEvent::Resized(physical_size) => {
+ env.surface =
+ create_surface(&env.windowed_context, &fb_info, &mut env.gr_context);
+ env.windowed_context.resize(physical_size)
+ }
+ WindowEvent::CloseRequested => {
+ warn!("renderer event loop stopped, telling the client to exit too");
+ SHOULD_EXIT.store(true, Ordering::Relaxed);
+ *control_flow = ControlFlow::Exit
+ }
+ WindowEvent::KeyboardInput {
+ input:
+ KeyboardInput {
+ virtual_keycode,
+ modifiers,
+ ..
+ },
+ ..
+ } => {
+ if modifiers.logo() {
+ if let Some(VirtualKeyCode::Q) = virtual_keycode {
+ *control_flow = ControlFlow::Exit;
+ }
+ }
+ env.windowed_context.window().request_redraw();
+ }
+ _ => (),
+ },
+ Event::RedrawRequested(_) => {
+ {
+ let dims = (env.surface.width() as f32, env.surface.height() as f32);
+ let canvas = env.surface.canvas();
+ renderer.draw(canvas, dims)
+ }
+ env.surface.canvas().flush();
+ env.windowed_context.swap_buffers().unwrap();
+ }
+ _ => (),
+ }
+ });
+}
+
+pub struct Renderer {
+ client_interface: ClientInterface,
+ map_renderer: MapRenderer,
+ world: World,
+}
+
+impl Renderer {
+ pub fn tick(&mut self) {
+ for m in self.client_interface.receive.try_iter() {
+ self.world.update(&m);
+ match m {
+ twclient::client::ClientMesgOut::MapChange { .. } => {
+ self.map_renderer.map_changed(&self.world)
+ }
+ _ => (),
+ }
+ }
+ }
+ pub fn draw(&mut self, canvas: &mut Canvas, dims: (f32, f32)) {
+ canvas.clear(Color::TRANSPARENT);
+ let center = self.world.local_tee().map(|t| (t.x, t.y)).unwrap_or((0, 0));
+
+ canvas.save();
+ canvas.translate((dims.0 / 2.0, dims.1 / 2.0));
+ canvas.translate((-center.0 as f32, -center.1 as f32));
+
+ self.map_renderer.draw(&self.world, canvas);
+
+ let tee_paint = Paint::new(
+ Color4f {
+ a: 1.0,
+ r: 1.0,
+ g: 0.0,
+ b: 1.0,
+ },
+ &ColorSpace::new_srgb(),
+ );
+ for t in self.world.tees.values() {
+ canvas.draw_circle(
+ Point {
+ x: t.x as f32,
+ y: t.y as f32,
+ },
+ 16.0,
+ &tee_paint,
+ );
+ }
+
+ canvas.restore();
+ }
+}
+
+fn create_surface(
+ windowed_context: &ContextWrapper<PossiblyCurrent, Window>,
+ fb_info: &FramebufferInfo,
+ gr_context: &mut skia_safe::gpu::DirectContext,
+) -> skia_safe::Surface {
+ let pixel_format = windowed_context.get_pixel_format();
+ let size = windowed_context.window().inner_size();
+ let backend_render_target = BackendRenderTarget::new_gl(
+ (
+ size.width.try_into().unwrap(),
+ size.height.try_into().unwrap(),
+ ),
+ pixel_format.multisampling.map(|s| s.try_into().unwrap()),
+ pixel_format.stencil_bits.try_into().unwrap(),
+ *fb_info,
+ );
+ Surface::from_backend_render_target(
+ gr_context,
+ &backend_render_target,
+ SurfaceOrigin::BottomLeft,
+ ColorType::RGBA8888,
+ None,
+ None,
+ )
+ .unwrap()
+}
diff --git a/renderer/src/map.rs b/renderer/src/map.rs
new file mode 100644
index 0000000..0ea3a6b
--- /dev/null
+++ b/renderer/src/map.rs
@@ -0,0 +1,163 @@
+use std::collections::HashMap;
+
+use log::{info, warn};
+use skia_safe::{
+ canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Paint, Rect,
+};
+use twclient::world::{
+ map::{format, TILE_NUM},
+ World,
+};
+
+pub struct MapRenderer {
+ tileset: HashMap<Option<usize>, Image>,
+}
+
+impl MapRenderer {
+ pub fn new() -> Self {
+ Self {
+ tileset: HashMap::new(),
+ }
+ }
+
+ pub fn tick(&mut self) {}
+ pub fn map_changed(&mut self, world: &World) {
+ self.tileset.clear();
+ for (key, t) in &world.map.tilesets {
+ let mut bytes: Vec<u8> = Vec::with_capacity(t.dim().0 * t.dim().1 * 4);
+ info!("loading tileset: {:?} => {:?}", key, t.dim());
+ for ((_x, _y), c) in t.indexed_iter() {
+ bytes.push(c.red);
+ bytes.push(c.green);
+ bytes.push(c.blue);
+ bytes.push(c.alpha);
+ }
+ let d = skia_safe::Data::new_copy(&bytes);
+ let v = skia_safe::Image::from_raster_data(
+ &skia_safe::ImageInfo::new(
+ ISize::new(t.dim().0 as i32, t.dim().1 as i32),
+ skia_safe::ColorType::RGBA8888,
+ skia_safe::AlphaType::Opaque,
+ None,
+ ),
+ d,
+ t.dim().0 * 4,
+ )
+ .unwrap();
+ self.tileset.insert(*key, v);
+ }
+ }
+
+ pub fn draw(&mut self, world: &World, canvas: &mut Canvas) {
+ let mut grid_paint = Paint::new(
+ Color4f {
+ a: 1.0,
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ },
+ &ColorSpace::new_srgb(),
+ );
+ grid_paint.set_style(skia_safe::PaintStyle::Stroke);
+ grid_paint.set_anti_alias(true);
+
+ let center = world
+ .local_tee()
+ .map(|t| (t.x / 16, t.y / 16))
+ .unwrap_or((0, 0));
+
+ let tile_rect = Rect {
+ top: center.1 as f32 * 16.0,
+ left: center.0 as f32 * 16.0,
+ bottom: center.1 as f32 * 16.0 + 16.0,
+ right: center.0 as f32 * 16.0 + 16.0,
+ };
+ canvas.draw_rect(tile_rect, &grid_paint);
+
+ for l in &world.map.layers {
+ let tileset = match world.map.tilesets.get(&l.image) {
+ Some(t) => t,
+ None => {
+ warn!("missing tileset for {:?}, skipping layer", l.image);
+ continue;
+ }
+ };
+ if tileset.dim() == (1, 1) {
+ continue;
+ }
+
+ for layer_y in (center.1 - 30)..(center.1 + 30) {
+ for layer_x in (center.0 - 30)..(center.0 + 30) {
+ let layer_x = layer_x.try_into().unwrap_or(0);
+ let layer_y = layer_y.try_into().unwrap_or(0);
+
+ if layer_x >= l.tiles.dim().1 || layer_y >= l.tiles.dim().0 {
+ continue;
+ }
+ let tile = l.tiles[(layer_y, layer_x)];
+
+ let _rotate = tile.flags & format::TILEFLAG_ROTATE != 0;
+ let _vflip = tile.flags & format::TILEFLAG_VFLIP != 0;
+ let _hflip = tile.flags & format::TILEFLAG_HFLIP != 0;
+ let tile_x = tile.index as u32 % TILE_NUM;
+ let tile_y = tile.index as u32 / TILE_NUM;
+
+ if tile_x == 0 && tile_y == 0 {
+ continue;
+ }
+
+ let tile_rect = Rect {
+ top: layer_y as f32 * 16.0,
+ left: layer_x as f32 * 16.0,
+ bottom: layer_y as f32 * 16.0 + 16.0,
+ right: layer_x as f32 * 16.0 + 16.0,
+ };
+ canvas.draw_rect(tile_rect, &grid_paint);
+
+ const TL: u32 = 16;
+ canvas.draw_image_rect(
+ self.tileset.get(&l.image).unwrap(),
+ Some((
+ &Rect {
+ top: (tile_y * TL) as f32,
+ left: (tile_x * TL) as f32,
+ bottom: ((tile_y + 1) * TL) as f32,
+ right: ((tile_x + 1) * TL) as f32,
+ },
+ SrcRectConstraint::Strict,
+ )),
+ tile_rect,
+ &Paint::default(),
+ );
+ // for iy in 0..TL {
+ // for ix in 0..TL {
+ // let (x, y) = ((layer_x as u32 * TL + iy), (layer_y as u32 * TL + ix));
+ // let p_tile =
+ // tileset[((tile_y * TL + iy) as usize, (tile_x * TL + ix) as usize)];
+ // let mut p = Paint::new(
+ // Color4f {
+ // a: 0.0,
+ // b: 0.0,
+ // g: 0.0,
+ // r: 0.0,
+ // },
+ // &ColorSpace::new_srgb(),
+ // );
+ // p.set_argb(p_tile.alpha, p_tile.red, p_tile.green, p_tile.green);
+ // p.set_style(PaintStyle::Fill);
+ // canvas.draw_rect(
+ // Rect {
+ // left: x as f32,
+ // top: y as f32,
+ // right: x as f32 + 1.0,
+ // bottom: y as f32 + 1.0,
+ // },
+ // &p,
+ // );
+ // }
+ // }
+ }
+ }
+ }
+ }
+}
diff --git a/snapshot/Cargo.toml b/snapshot/Cargo.toml
new file mode 100644
index 0000000..165ba3c
--- /dev/null
+++ b/snapshot/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "snapshot"
+version = "0.0.1"
+authors = [
+ "metamuffin <metamuffin@disroot.org>",
+ "heinrich5991 <heinrich5991@gmail.com>",
+]
+license = "AGPL-3.0-only"
+
+[dependencies]
+buffer = "0.1.9"
+common = { git = "https://github.com/heinrich5991/libtw2" }
+gamenet_teeworlds_0_5 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_teeworlds_0_6 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_teeworlds_0_7 = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+gamenet_ddnet = { git = "https://github.com/heinrich5991/libtw2", optional = true }
+packer = { git = "https://github.com/heinrich5991/libtw2" }
+vec_map = "0.8.0"
+warn = ">=0.1.1,<0.3.0"
+
+
+[features]
+default = ["gamenet_ddnet"]
+gamenet_tw_0_5 = ["gamenet_teeworlds_0_5"]
+gamenet_tw_0_6 = ["gamenet_teeworlds_0_6"]
+gamenet_tw_0_7 = ["gamenet_teeworlds_0_7"]
+gamenet_ddnet_0_6 = ["gamenet_ddnet"]
diff --git a/snapshot/src/format.rs b/snapshot/src/format.rs
new file mode 100644
index 0000000..1a22938
--- /dev/null
+++ b/snapshot/src/format.rs
@@ -0,0 +1,85 @@
+use buffer::CapacityError;
+use packer::Packer;
+use packer::Unpacker;
+use packer;
+use snap::Error;
+use warn::Warn;
+use warn::wrap;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Warning {
+ Packer(packer::Warning),
+ NonZeroPadding,
+ DuplicateDelete,
+ DuplicateUpdate,
+ UnknownDelete,
+ DeleteUpdate,
+ NumUpdatedItems,
+}
+
+impl From<packer::Warning> for Warning {
+ fn from(w: packer::Warning) -> Warning {
+ Warning::Packer(w)
+ }
+}
+
+pub fn key_to_type_id(key: i32) -> u16 {
+ ((key as u32 >> 16) & 0xffff) as u16
+}
+
+pub fn key_to_id(key: i32) -> u16 {
+ ((key as u32) & 0xffff) as u16
+}
+
+pub fn key(type_id: u16, id: u16) -> i32 {
+ (((type_id as u32) << 16) | (id as u32)) as i32
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Item<'a> {
+ pub type_id: u16,
+ pub id: u16,
+ pub data: &'a [i32],
+}
+
+impl<'a> Item<'a> {
+ pub fn from_key(key: i32, data: &[i32]) -> Item {
+ Item {
+ type_id: key_to_type_id(key),
+ id: key_to_id(key),
+ data: data,
+ }
+ }
+ pub fn key(&self) -> i32 {
+ key(self.type_id, self.id)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct DeltaHeader {
+ pub num_deleted_items: i32,
+ pub num_updated_items: i32,
+}
+
+impl DeltaHeader {
+ pub fn decode<W: Warn<Warning>>(warn: &mut W, p: &mut Unpacker)
+ -> Result<DeltaHeader, Error>
+ {
+ let result = DeltaHeader {
+ num_deleted_items: packer::positive(p.read_int(wrap(warn))?)?,
+ num_updated_items: packer::positive(p.read_int(wrap(warn))?)?,
+ };
+ if p.read_int(wrap(warn))? != 0 {
+ warn.warn(Warning::NonZeroPadding);
+ }
+ Ok(result)
+ }
+ pub fn encode<'d, 's>(&self, mut p: Packer<'d, 's>)
+ -> Result<&'d [u8], CapacityError>
+ {
+ p.write_int(self.num_deleted_items)?;
+ p.write_int(self.num_updated_items)?;
+ p.write_int(0)?;
+ Ok(p.written())
+ }
+}
diff --git a/snapshot/src/lib.rs b/snapshot/src/lib.rs
new file mode 100644
index 0000000..992d898
--- /dev/null
+++ b/snapshot/src/lib.rs
@@ -0,0 +1,36 @@
+
+#[cfg(feature = "gamenet_ddnet")]
+extern crate gamenet_ddnet as gamenet;
+#[cfg(feature = "gamenet_0_5")]
+extern crate gamenet_teeworlds_0_5 as gamenet;
+#[cfg(feature = "gamenet_0_6")]
+extern crate gamenet_teeworlds_0_6 as gamenet;
+#[cfg(feature = "gamenet_0_7")]
+extern crate gamenet_teeworlds_0_7 as gamenet;
+
+extern crate buffer;
+extern crate common;
+extern crate packer;
+extern crate vec_map;
+extern crate warn;
+
+pub mod format;
+pub mod manager;
+pub mod receiver;
+pub mod snap;
+pub mod storage;
+
+pub use manager::Manager;
+pub use receiver::DeltaReceiver;
+pub use receiver::ReceivedDelta;
+pub use snap::Delta;
+pub use snap::DeltaReader;
+pub use snap::Snap;
+pub use storage::Storage;
+
+use common::num::Cast;
+use std::ops;
+
+fn to_usize(r: ops::Range<u32>) -> ops::Range<usize> {
+ r.start.usize()..r.end.usize()
+}
diff --git a/snapshot/src/manager.rs b/snapshot/src/manager.rs
new file mode 100644
index 0000000..65d0c72
--- /dev/null
+++ b/snapshot/src/manager.rs
@@ -0,0 +1,140 @@
+use Delta;
+use DeltaReader;
+use DeltaReceiver;
+use ReceivedDelta;
+use Snap;
+use Storage;
+use format;
+use gamenet::msg::system;
+use packer::Unpacker;
+use receiver;
+use snap;
+use storage;
+use warn::Warn;
+use warn::wrap;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Error {
+ Receiver(receiver::Error),
+ Snap(snap::Error),
+ Storage(storage::Error),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Warning {
+ Receiver(receiver::Warning),
+ Snap(format::Warning),
+ Storage(storage::Warning),
+}
+
+impl From<receiver::Error> for Error {
+ fn from(err: receiver::Error) -> Error {
+ Error::Receiver(err)
+ }
+}
+
+impl From<snap::Error> for Error {
+ fn from(err: snap::Error) -> Error {
+ Error::Snap(err)
+ }
+}
+
+impl From<storage::Error> for Error {
+ fn from(err: storage::Error) -> Error {
+ Error::Storage(err)
+ }
+}
+
+impl From<receiver::Warning> for Warning {
+ fn from(w: receiver::Warning) -> Warning {
+ Warning::Receiver(w)
+ }
+}
+
+impl From<format::Warning> for Warning {
+ fn from(w: format::Warning) -> Warning {
+ Warning::Snap(w)
+ }
+}
+
+impl From<storage::Warning> for Warning {
+ fn from(w: storage::Warning) -> Warning {
+ Warning::Storage(w)
+ }
+}
+
+#[derive(Clone, Default)]
+struct ManagerInner {
+ temp_delta: Delta,
+ reader: DeltaReader,
+ storage: Storage,
+}
+
+#[derive(Clone, Default)]
+pub struct Manager {
+ inner: ManagerInner,
+ receiver: DeltaReceiver,
+}
+
+impl Manager {
+ pub fn new() -> Manager {
+ Default::default()
+ }
+ pub fn reset(&mut self) {
+ self.inner.storage.reset();
+ self.receiver.reset();
+ }
+ pub fn ack_tick(&self) -> Option<i32> {
+ self.inner.storage.ack_tick()
+ }
+ pub fn snap_empty<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapEmpty)
+ -> Result<Option<&Snap>, Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ let res = self.receiver.snap_empty(wrap(warn), snap);
+ self.inner.handle_msg(warn, object_size, res)
+ }
+ pub fn snap_single<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapSingle)
+ -> Result<Option<&Snap>, Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ let res = self.receiver.snap_single(wrap(warn), snap);
+ self.inner.handle_msg(warn, object_size, res)
+ }
+ pub fn snap<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::Snap)
+ -> Result<Option<&Snap>, Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ let res = self.receiver.snap(wrap(warn), snap);
+ self.inner.handle_msg(warn, object_size, res)
+ }
+}
+
+impl ManagerInner {
+ fn handle_msg<W, O>(&mut self, warn: &mut W, object_size: O, res: Result<Option<ReceivedDelta>, receiver::Error>)
+ -> Result<Option<&Snap>, Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ Ok(match res? {
+ Some(delta) => Some(self.add_delta(warn, object_size, delta)?),
+ None => None,
+ })
+ }
+ fn add_delta<W, O>(&mut self, warn: &mut W, object_size: O, delta: ReceivedDelta)
+ -> Result<&Snap, Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ let crc = delta.data_and_crc.map(|d| d.1);
+ if let Some((data, _)) = delta.data_and_crc {
+ self.reader.read(wrap(warn), &mut self.temp_delta, object_size, &mut Unpacker::new(data))?;
+ } else {
+ self.temp_delta.clear();
+ }
+ Ok(self.storage.add_delta(wrap(warn), crc, delta.delta_tick, delta.tick, &self.temp_delta)?)
+ }
+}
diff --git a/snapshot/src/receiver.rs b/snapshot/src/receiver.rs
new file mode 100644
index 0000000..49833d1
--- /dev/null
+++ b/snapshot/src/receiver.rs
@@ -0,0 +1,245 @@
+use common::num::Cast;
+use gamenet::msg::system;
+use std::ops;
+use to_usize;
+use vec_map::VecMap;
+use warn::Warn;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Warning {
+ DuplicateSnap,
+ DifferingAttributes,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Error {
+ OldDelta,
+ InvalidNumParts,
+ InvalidPart,
+ DuplicatePart,
+}
+
+// TODO: How to handle `tick` overflowing?
+#[derive(Clone, Debug)]
+struct CurrentDelta {
+ tick: i32,
+ delta_tick: i32,
+ num_parts: i32,
+ crc: i32,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct ReceivedDelta<'a> {
+ pub delta_tick: i32,
+ pub tick: i32,
+ pub data_and_crc: Option<(&'a [u8], i32)>,
+}
+
+#[derive(Clone, Default)]
+pub struct DeltaReceiver {
+ previous_tick: Option<i32>,
+ current: Option<CurrentDelta>,
+ // `parts` points into `receive_buf`.
+ parts: VecMap<ops::Range<u32>>,
+ receive_buf: Vec<u8>,
+ result: Vec<u8>,
+}
+
+impl DeltaReceiver {
+ pub fn new() -> DeltaReceiver {
+ Default::default()
+ }
+ pub fn reset(&mut self) {
+ self.previous_tick = None;
+ self.current = None;
+ }
+ fn can_receive(&self, tick: i32) -> bool {
+ self.current.as_ref().map(|c| c.tick <= tick)
+ .or(self.previous_tick.map(|t| t < tick))
+ .unwrap_or(true)
+ }
+ fn init_delta(&mut self) {
+ self.parts.clear();
+ self.receive_buf.clear();
+ self.result.clear();
+ }
+ fn finish_delta(&mut self, tick: i32) {
+ self.current = None;
+ self.previous_tick = Some(tick);
+ }
+ pub fn snap_empty<W>(&mut self, warn: &mut W, snap: system::SnapEmpty)
+ -> Result<Option<ReceivedDelta>, Error>
+ where W: Warn<Warning>,
+ {
+ if !self.can_receive(snap.tick) {
+ return Err(Error::OldDelta);
+ }
+ if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) {
+ warn.warn(Warning::DuplicateSnap);
+ }
+ self.init_delta();
+ self.finish_delta(snap.tick);
+ Ok(Some(ReceivedDelta {
+ delta_tick: snap.tick.wrapping_sub(snap.delta_tick),
+ tick: snap.tick,
+ data_and_crc: None,
+ }))
+ }
+ pub fn snap_single<W>(&mut self, warn: &mut W, snap: system::SnapSingle)
+ -> Result<Option<ReceivedDelta>, Error>
+ where W: Warn<Warning>,
+ {
+ if !self.can_receive(snap.tick) {
+ return Err(Error::OldDelta);
+ }
+ if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) {
+ warn.warn(Warning::DuplicateSnap);
+ }
+ self.init_delta();
+ self.finish_delta(snap.tick);
+ self.result.extend(snap.data);
+ Ok(Some(ReceivedDelta {
+ delta_tick: snap.tick.wrapping_sub(snap.delta_tick),
+ tick: snap.tick,
+ data_and_crc: Some((&self.result, snap.crc)),
+ }))
+ }
+ pub fn snap<W>(&mut self, warn: &mut W, snap: system::Snap)
+ -> Result<Option<ReceivedDelta>, Error>
+ where W: Warn<Warning>,
+ {
+ if !self.can_receive(snap.tick) {
+ return Err(Error::OldDelta);
+ }
+ if !(0 <= snap.num_parts && snap.num_parts <= 32) {
+ return Err(Error::InvalidNumParts);
+ }
+ if !(0 <= snap.part && snap.part < snap.num_parts) {
+ return Err(Error::InvalidPart);
+ }
+ if self.current.as_ref().map(|c| c.tick != snap.tick).unwrap_or(false) {
+ self.current = None;
+ }
+ if let None = self.current {
+ self.init_delta();
+ self.current = Some(CurrentDelta {
+ tick: snap.tick,
+ delta_tick: snap.tick.wrapping_sub(snap.delta_tick),
+ num_parts: snap.num_parts,
+ crc: snap.crc,
+ });
+
+ // Checked above.
+ let num_parts = snap.num_parts.assert_usize();
+ self.parts.reserve_len(num_parts);
+ }
+ let delta_tick;
+ let tick;
+ let crc;
+ let num_parts;
+ {
+ let current: &mut CurrentDelta = self.current.as_mut().unwrap();
+ if snap.delta_tick != current.delta_tick
+ || snap.num_parts != current.num_parts
+ || snap.crc != current.crc
+ {
+ warn.warn(Warning::DifferingAttributes);
+ }
+ delta_tick = current.delta_tick;
+ tick = current.tick;
+ crc = current.crc;
+ num_parts = current.num_parts;
+ }
+ let part = snap.part.assert_usize();
+ if self.parts.contains_key(part) {
+ return Err(Error::DuplicatePart);
+ }
+ let start = self.receive_buf.len().assert_u32();
+ let end = (self.receive_buf.len() + snap.data.len()).assert_u32();
+ self.receive_buf.extend(snap.data);
+ assert!(self.parts.insert(part, start..end).is_none());
+
+ if self.parts.len().assert_i32() != num_parts {
+ return Ok(None);
+ }
+
+ self.finish_delta(tick);
+ self.result.reserve(self.receive_buf.len());
+ for range in self.parts.values() {
+ self.result.extend(&self.receive_buf[to_usize(range.clone())]);
+ }
+
+ Ok(Some(ReceivedDelta {
+ delta_tick: delta_tick,
+ tick: tick,
+ data_and_crc: Some((&self.result, crc)),
+ }))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use common::num::Cast;
+ use gamenet::msg::system::Snap;
+ use gamenet::msg::system::SnapEmpty;
+ use gamenet::msg::system::SnapSingle;
+ use super::DeltaReceiver;
+ use super::Error;
+ use super::ReceivedDelta;
+ use warn::Panic;
+
+ #[test]
+ fn old() {
+ let mut receiver = DeltaReceiver::new();
+ {
+ let result = receiver.snap_empty(&mut Panic, SnapEmpty {
+ tick: 1,
+ delta_tick: 2,
+ }).unwrap();
+
+ assert_eq!(result, Some(ReceivedDelta {
+ delta_tick: -1,
+ tick: 1,
+ data_and_crc: None,
+ }));
+ }
+
+ assert_eq!(receiver.snap_single(&mut Panic, SnapSingle {
+ tick: 0,
+ delta_tick: 0,
+ data: b"123",
+ crc: 0,
+ }).unwrap_err(), Error::OldDelta);
+ }
+
+ #[test]
+ fn reorder() {
+ let mut receiver = DeltaReceiver::new();
+ let chunks: &[(i32, &[u8])] = &[
+ (3, b"3"),
+ (2, b"2"),
+ (4, b"4_"),
+ (1, b"1__"),
+ (0, b"0"),
+ ];
+ for &(i, c) in chunks {
+ let result = receiver.snap(&mut Panic, Snap {
+ tick: 2,
+ delta_tick: 1,
+ num_parts: chunks.len().assert_i32(),
+ part: i,
+ crc: 3,
+ data: c,
+ }).unwrap();
+ if i != 0 {
+ assert_eq!(result, None);
+ } else {
+ assert_eq!(result, Some(ReceivedDelta {
+ delta_tick: 1,
+ tick: 2,
+ data_and_crc: Some((b"01__234_", 3)),
+ }));
+ }
+ }
+ }
+}
diff --git a/snapshot/src/snap.rs b/snapshot/src/snap.rs
new file mode 100644
index 0000000..7b9768f
--- /dev/null
+++ b/snapshot/src/snap.rs
@@ -0,0 +1,511 @@
+use buffer::CapacityError;
+use common::num::Cast;
+use format::DeltaHeader;
+use format::Item;
+use format::Warning;
+use format::key;
+use format::key_to_id;
+use format::key_to_type_id;
+use gamenet::enums::MAX_SNAPSHOT_PACKSIZE;
+use gamenet::msg::system;
+use packer::Packer;
+use packer::Unpacker;
+use packer::with_packer;
+use packer;
+use std::cmp;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::collections::hash_map;
+use std::fmt;
+use std::iter;
+use std::mem;
+use std::ops;
+use to_usize;
+use warn::Warn;
+use warn::wrap;
+
+// TODO: Actually obey this the same way as Teeworlds does.
+pub const MAX_SNAPSHOT_SIZE: usize = 64 * 1024; // 64 KB
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Error {
+ UnexpectedEnd,
+ IntOutOfRange,
+ DeletedItemsUnpacking,
+ ItemDiffsUnpacking,
+ TypeIdRange,
+ IdRange,
+ NegativeSize,
+ TooLongDiff,
+ TooLongSnap,
+ DeltaDifferingSizes,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum BuilderError {
+ DuplicateKey,
+ TooLongSnap,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct TooLongSnap;
+
+impl From<TooLongSnap> for Error {
+ fn from(_: TooLongSnap) -> Error {
+ Error::TooLongSnap
+ }
+}
+
+impl From<TooLongSnap> for BuilderError {
+ fn from(_: TooLongSnap) -> BuilderError {
+ BuilderError::TooLongSnap
+ }
+}
+
+impl From<packer::IntOutOfRange> for Error {
+ fn from(_: packer::IntOutOfRange) -> Error {
+ Error::IntOutOfRange
+ }
+}
+
+impl From<packer::UnexpectedEnd> for Error {
+ fn from(_: packer::UnexpectedEnd) -> Error {
+ Error::UnexpectedEnd
+ }
+}
+
+fn apply_delta(in_: Option<&[i32]>, delta: &[i32], out: &mut [i32])
+ -> Result<(), Error>
+{
+ assert!(delta.len() == out.len());
+ match in_ {
+ Some(in_) => {
+ if in_.len() != out.len() {
+ return Err(Error::DeltaDifferingSizes);
+ }
+ for i in 0..out.len() {
+ out[i] = in_[i].wrapping_add(delta[i]);
+ }
+ }
+ None => out.copy_from_slice(delta),
+ }
+ Ok(())
+}
+
+fn create_delta(from: Option<&[i32]>, to: &[i32], out: &mut [i32]) {
+ assert!(to.len() == out.len());
+ match from {
+ Some(from) => {
+ assert!(from.len() == to.len());
+ for i in 0..out.len() {
+ out[i] = to[i].wrapping_sub(from[i]);
+ }
+ },
+ None => out.copy_from_slice(to),
+ }
+}
+
+// TODO: Select a faster hasher?
+#[derive(Clone, Default)]
+pub struct Snap {
+ offsets: HashMap<i32, ops::Range<u32>>,
+ buf: Vec<i32>,
+}
+
+impl Snap {
+ pub fn empty() -> Snap {
+ Default::default()
+ }
+ fn clear(&mut self) {
+ self.offsets.clear();
+ self.buf.clear();
+ }
+ fn item_from_offset(&self, offset: ops::Range<u32>) -> &[i32] {
+ &self.buf[to_usize(offset)]
+ }
+ pub fn item(&self, type_id: u16, id: u16) -> Option<&[i32]> {
+ self.offsets.get(&key(type_id, id)).map(|o| &self.buf[to_usize(o.clone())])
+ }
+ pub fn items(&self) -> Items {
+ Items {
+ snap: self,
+ iter: self.offsets.iter(),
+ }
+ }
+ fn prepare_item_vacant<'a>(entry: hash_map::VacantEntry<'a, i32, ops::Range<u32>>, buf: &mut Vec<i32>, size: usize)
+ -> Result<&'a mut ops::Range<u32>, TooLongSnap>
+ {
+ let offset = buf.len();
+ if offset + size > MAX_SNAPSHOT_SIZE {
+ return Err(TooLongSnap);
+ }
+ let start = offset.assert_u32();
+ let end = (offset + size).assert_u32();
+ buf.extend(iter::repeat(0).take(size));
+ Ok(entry.insert(start..end))
+ }
+ fn prepare_item(&mut self, type_id: u16, id: u16, size: usize)
+ -> Result<&mut [i32], Error>
+ {
+ let offset = match self.offsets.entry(key(type_id, id)) {
+ hash_map::Entry::Occupied(o) => o.into_mut(),
+ hash_map::Entry::Vacant(v) => Snap::prepare_item_vacant(v, &mut self.buf, size)?,
+ }.clone();
+ Ok(&mut self.buf[to_usize(offset)])
+ }
+ pub fn read_with_delta<W>(&mut self, warn: &mut W, from: &Snap, delta: &Delta)
+ -> Result<(), Error>
+ where W: Warn<Warning>,
+ {
+ self.clear();
+
+ let mut num_deletions = 0;
+ for item in from.items() {
+ if !delta.deleted_items.contains(&item.key()) {
+ let out = self.prepare_item(item.type_id, item.id, item.data.len())?;
+ out.copy_from_slice(item.data);
+ } else {
+ num_deletions += 1;
+ }
+ }
+ if num_deletions != delta.deleted_items.len() {
+ warn.warn(Warning::UnknownDelete);
+ }
+
+ for (&key, offset) in &delta.updated_items {
+ let type_id = key_to_type_id(key);
+ let id = key_to_id(key);
+ let diff = &delta.buf[to_usize(offset.clone())];
+ let out = self.prepare_item(type_id, id, diff.len())?;
+ let in_ = from.item(type_id, id);
+
+ apply_delta(in_, diff, out)?;
+ }
+ Ok(())
+ }
+ pub fn write<'d, 's>(&self, buf: &mut Vec<i32>, mut p: Packer<'d, 's>)
+ -> Result<&'d [u8], CapacityError>
+ {
+ let keys = buf;
+ keys.clear();
+ keys.extend(self.offsets.keys().cloned());
+ keys.sort_unstable_by_key(|&k| k as u32);
+ let data_size = self.buf.len()
+ .checked_add(self.offsets.len()).expect("snap size overflow")
+ .checked_mul(mem::size_of::<i32>()).expect("snap size overflow")
+ .assert_i32();
+ p.write_int(data_size)?;
+ let num_items = self.offsets.len().assert_i32();
+ p.write_int(num_items)?;
+
+ let mut offset = 0;
+ for &key in &*keys {
+ p.write_int(offset)?;
+ let key_offset = self.offsets[&key].clone();
+ offset = offset
+ .checked_add((key_offset.end - key_offset.start + 1).usize()
+ .checked_mul(mem::size_of::<i32>())
+ .expect("item size overflow").assert_i32())
+ .expect("offset overflow");
+ }
+ for &key in &*keys {
+ p.write_int(key)?;
+ for &i in &self.buf[to_usize(self.offsets[&key].clone())] {
+ p.write_int(i)?;
+ }
+ }
+ Ok(p.written())
+ }
+ pub fn crc(&self) -> i32 {
+ self.buf.iter().fold(0, |s, &a| s.wrapping_add(a))
+ }
+ pub fn recycle(mut self) -> Builder {
+ self.clear();
+ Builder {
+ snap: self,
+ }
+ }
+}
+
+pub struct Items<'a> {
+ snap: &'a Snap,
+ iter: hash_map::Iter<'a, i32, ops::Range<u32>>,
+}
+
+impl<'a> Iterator for Items<'a> {
+ type Item = Item<'a>;
+ fn next(&mut self) -> Option<Item<'a>> {
+ self.iter.next().map(|(&k, o)| {
+ Item::from_key(k, self.snap.item_from_offset(o.clone()))
+ })
+ }
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+impl<'a> ExactSizeIterator for Items<'a> {
+ fn len(&self) -> usize {
+ self.iter.len()
+ }
+}
+
+impl fmt::Debug for Snap {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_map().entries(self.items().map(
+ |Item { type_id, id, data }| ((type_id, id), data)
+ )).finish()
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct Delta {
+ deleted_items: HashSet<i32>,
+ updated_items: HashMap<i32, ops::Range<u32>>,
+ buf: Vec<i32>,
+}
+
+impl Delta {
+ pub fn new() -> Delta {
+ Default::default()
+ }
+ pub fn clear(&mut self) {
+ self.deleted_items.clear();
+ self.updated_items.clear();
+ self.buf.clear();
+ }
+ fn prepare_update_item(&mut self, type_id: u16, id: u16, size: usize) -> &mut [i32] {
+ let key = key(type_id, id);
+
+ let offset = self.buf.len();
+ let start = offset.assert_u32();
+ let end = (offset + size).assert_u32();
+ self.buf.extend(iter::repeat(0).take(size));
+ assert!(self.updated_items.insert(key, start..end).is_none());
+ &mut self.buf[to_usize(start..end)]
+ }
+ pub fn create(&mut self, from: &Snap, to: &Snap) {
+ self.clear();
+ for Item { type_id, id, .. } in from.items() {
+ if to.item(type_id, id).is_none() {
+ assert!(self.deleted_items.insert(key(type_id, id)));
+ }
+ }
+ for Item { type_id, id, data } in to.items() {
+ let from_data = from.item(type_id, id);
+ let out_delta = self.prepare_update_item(type_id, id, data.len());
+ create_delta(from_data, data, out_delta);
+ }
+ }
+ pub fn write<'d, 's, O>(&self, object_size: O, mut p: Packer<'d, 's>)
+ -> Result<&'d [u8], CapacityError>
+ where O: FnMut(u16) -> Option<u32>,
+ {
+ let mut object_size = object_size;
+ with_packer(&mut p, |p| DeltaHeader {
+ num_deleted_items: self.deleted_items.len().assert_i32(),
+ num_updated_items: self.updated_items.len().assert_i32()
+ }.encode(p))?;
+ for &key in &self.deleted_items {
+ p.write_int(key)?;
+ }
+ for (&key, range) in &self.updated_items {
+ let data = &self.buf[to_usize(range.clone())];
+ let type_id = key_to_type_id(key);
+ let id = key_to_id(key);
+ p.write_int(type_id.i32())?;
+ p.write_int(id.i32())?;
+ match object_size(type_id) {
+ Some(size) => assert!(size.usize() == data.len()),
+ None => p.write_int(data.len().assert_i32())?,
+ }
+ for &d in data {
+ p.write_int(d)?;
+ }
+ }
+ Ok(p.written())
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct DeltaReader {
+ buf: Vec<i32>,
+}
+
+impl DeltaReader {
+ pub fn new() -> DeltaReader {
+ Default::default()
+ }
+ fn clear(&mut self) {
+ self.buf.clear();
+ }
+ pub fn read<W, O>(&mut self, warn: &mut W, delta: &mut Delta, object_size: O, p: &mut Unpacker)
+ -> Result<(), Error>
+ where W: Warn<Warning>,
+ O: FnMut(u16) -> Option<u32>,
+ {
+ delta.clear();
+ self.clear();
+
+ let mut object_size = object_size;
+
+ let header = DeltaHeader::decode(warn, p)?;
+ while !p.as_slice().is_empty() {
+ self.buf.push(p.read_int(wrap(warn))?);
+ }
+ let split = header.num_deleted_items.assert_usize();
+ if split > self.buf.len() {
+ return Err(Error::DeletedItemsUnpacking);
+ }
+ let (deleted_items, buf) = self.buf.split_at(split);
+ delta.deleted_items.extend(deleted_items);
+ if deleted_items.len() != delta.deleted_items.len() {
+ warn.warn(Warning::DuplicateDelete);
+ }
+
+ let mut num_updates = 0;
+ let mut buf = buf.iter();
+ // FIXME: Use `is_empty`.
+ while buf.len() != 0 {
+ let type_id = buf.next().ok_or(Error::ItemDiffsUnpacking)?;
+ let id = buf.next().ok_or(Error::ItemDiffsUnpacking)?;
+
+ let type_id = type_id.try_u16().ok_or(Error::TypeIdRange)?;
+ let id = id.try_u16().ok_or(Error::IdRange)?;
+
+ let size = match object_size(type_id) {
+ Some(s) => s.usize(),
+ None => {
+ let s = buf.next().ok_or(Error::ItemDiffsUnpacking)?;
+ s.try_usize().ok_or(Error::NegativeSize)?
+ }
+ };
+
+ if size > buf.len() {
+ return Err(Error::ItemDiffsUnpacking);
+ }
+ let (data, b) = buf.as_slice().split_at(size);
+ buf = b.iter();
+
+ let offset = delta.buf.len();
+ let start = offset.try_u32().ok_or(Error::TooLongDiff)?;
+ let end = (offset + data.len()).try_u32().ok_or(Error::TooLongDiff)?;
+ delta.buf.extend(data.iter());
+
+ // In case of conflict, take later update (as the original code does).
+ if delta.updated_items.insert(key(type_id, id), start..end).is_some() {
+ warn.warn(Warning::DuplicateUpdate);
+ }
+
+ if delta.deleted_items.contains(&key(type_id, id)) {
+ warn.warn(Warning::DeleteUpdate);
+ }
+ num_updates += 1;
+ }
+
+ if num_updates != header.num_updated_items {
+ warn.warn(Warning::NumUpdatedItems);
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Default)]
+pub struct Builder {
+ snap: Snap,
+}
+
+impl Builder {
+ pub fn new() -> Builder {
+ Default::default()
+ }
+ pub fn add_item(&mut self, type_id: u16, id: u16, data: &[i32])
+ -> Result<(), BuilderError>
+ {
+ let offset = match self.snap.offsets.entry(key(type_id, id)) {
+ hash_map::Entry::Occupied(..) => return Err(BuilderError::DuplicateKey),
+ hash_map::Entry::Vacant(v) => {
+ Snap::prepare_item_vacant(v, &mut self.snap.buf, data.len())?
+ }
+ }.clone();
+ self.snap.buf[to_usize(offset)].copy_from_slice(data);
+ Ok(())
+ }
+ pub fn finish(self) -> Snap {
+ self.snap
+ }
+}
+
+pub fn delta_chunks(tick: i32, delta_tick: i32, data: &[u8], crc: i32) -> DeltaChunks {
+ DeltaChunks {
+ tick: tick,
+ delta_tick: tick - delta_tick,
+ crc: crc,
+ cur_part: if !data.is_empty() { 0 } else { -1 },
+ num_parts: ((data.len() + MAX_SNAPSHOT_PACKSIZE as usize - 1) / MAX_SNAPSHOT_PACKSIZE as usize).assert_i32(),
+ data: data,
+ }
+}
+
+impl<'a> Into<system::System<'a>> for SnapMsg<'a> {
+ fn into(self) -> system::System<'a> {
+ match self {
+ SnapMsg::Snap(s) => system::System::Snap(s),
+ SnapMsg::SnapEmpty(s) => system::System::SnapEmpty(s),
+ SnapMsg::SnapSingle(s) => system::System::SnapSingle(s),
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum SnapMsg<'a> {
+ Snap(system::Snap<'a>),
+ SnapEmpty(system::SnapEmpty),
+ SnapSingle(system::SnapSingle<'a>),
+}
+
+pub struct DeltaChunks<'a> {
+ tick: i32,
+ delta_tick: i32,
+ crc: i32,
+ cur_part: i32,
+ num_parts: i32,
+ data: &'a [u8],
+}
+
+impl<'a> Iterator for DeltaChunks<'a> {
+ type Item = SnapMsg<'a>;
+ fn next(&mut self) -> Option<SnapMsg<'a>> {
+ if self.cur_part == self.num_parts {
+ return None;
+ }
+ let result = if self.num_parts == 0 {
+ SnapMsg::SnapEmpty(system::SnapEmpty {
+ tick: self.tick,
+ delta_tick: self.delta_tick,
+ })
+ } else if self.num_parts == 1 {
+ SnapMsg::SnapSingle(system::SnapSingle {
+ tick: self.tick,
+ delta_tick: self.delta_tick,
+ crc: self.crc,
+ data: self.data,
+ })
+ } else {
+ let index = self.cur_part.assert_usize();
+ let start = MAX_SNAPSHOT_PACKSIZE as usize * index;
+ let end = cmp::min(MAX_SNAPSHOT_PACKSIZE as usize * (index + 1), self.data.len());
+ SnapMsg::Snap(system::Snap {
+ tick: self.tick,
+ delta_tick: self.delta_tick,
+ num_parts: self.num_parts,
+ part: self.cur_part,
+ crc: self.crc,
+ data: &self.data[start..end],
+ })
+ };
+ self.cur_part += 1;
+ Some(result)
+ }
+}
diff --git a/snapshot/src/storage.rs b/snapshot/src/storage.rs
new file mode 100644
index 0000000..c445d04
--- /dev/null
+++ b/snapshot/src/storage.rs
@@ -0,0 +1,176 @@
+use format;
+use snap::Builder;
+use snap::Delta;
+use snap::Snap;
+use snap;
+use std::collections::VecDeque;
+use warn::Warn;
+use warn::wrap;
+
+// TODO: Separate server storage from client storage.
+// TODO: Delete snapshots over time.
+
+#[derive(Clone)]
+struct StoredSnap {
+ snap: Snap,
+ tick: i32,
+}
+
+const MAX_STORED_SNAPSHOT: usize = 100;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct UnknownSnap;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct WeirdNegativeDeltaTick;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Error {
+ OldDelta,
+ UnknownSnap,
+ InvalidCrc,
+ Unpack(snap::Error),
+}
+
+impl From<snap::Error> for Error {
+ fn from(err: snap::Error) -> Error {
+ Error::Unpack(err)
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Warning {
+ WeirdNegativeDeltaTick,
+ Unpack(format::Warning),
+}
+
+impl From<format::Warning> for Warning {
+ fn from(w: format::Warning) -> Warning {
+ Warning::Unpack(w)
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct Storage {
+ /// Queue that stores received snaps.
+ ///
+ /// The newest elements are in the front.
+ snaps: VecDeque<StoredSnap>,
+ free: Vec<Snap>,
+ ack_tick: Option<i32>,
+ delta: Delta,
+ delta_tick: Option<i32>,
+}
+
+impl Storage {
+ pub fn new() -> Storage {
+ Default::default()
+ }
+ pub fn reset(&mut self) {
+ let self_free = &mut self.free;
+ // FIXME: Replace with something like `exhaust`.
+ self.snaps.drain(..).map(|s| self_free.push(s.snap)).count();
+ self.ack_tick = None;
+ }
+ pub fn ack_tick(&self) -> Option<i32> {
+ self.ack_tick
+ }
+ pub fn add_delta<W>(&mut self, warn: &mut W, crc: Option<i32>, delta_tick: i32, tick: i32, delta: &Delta)
+ -> Result<&Snap, Error>
+ where W: Warn<Warning>,
+ {
+ if self.snaps.front().map(|s| s.tick).unwrap_or(-1) >= tick {
+ return Err(Error::OldDelta);
+ }
+ {
+ let empty = Snap::empty();
+ let delta_snap;
+ if delta_tick >= 0 {
+ if let Some(i) = self.snaps.iter().position(|s| s.tick < delta_tick) {
+ let self_free = &mut self.free;
+ // FIXME: Replace with something like `exhaust`.
+ self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count();
+ }
+ if let Some(d) = self.snaps.back() {
+ if d.tick == delta_tick {
+ delta_snap = &d.snap;
+ } else {
+ self.ack_tick = None;
+ return Err(Error::UnknownSnap);
+ }
+ } else {
+ self.ack_tick = None;
+ return Err(Error::UnknownSnap);
+ }
+ } else {
+ delta_snap = &empty;
+ if delta_tick != -1 {
+ warn.warn(Warning::WeirdNegativeDeltaTick);
+ }
+ }
+ if self.free.is_empty() {
+ self.free.push(Snap::empty());
+ }
+
+ let new_snap: &mut Snap = self.free.last_mut().unwrap();
+ new_snap.read_with_delta(wrap(warn), delta_snap, delta)?;
+ if crc.map(|crc| crc != new_snap.crc()).unwrap_or(false) {
+ self.ack_tick = None;
+ return Err(Error::InvalidCrc);
+ }
+ self.ack_tick = Some(tick);
+ }
+ self.snaps.push_front(StoredSnap {
+ tick: tick,
+ snap: self.free.pop().unwrap(),
+ });
+ if self.snaps.len() > MAX_STORED_SNAPSHOT {
+ self.free.push(self.snaps.pop_back().unwrap().snap);
+ }
+ Ok(&self.snaps.front().unwrap().snap)
+ }
+ pub fn new_builder(&mut self) -> Builder {
+ self.free.pop().unwrap_or_default().recycle()
+ }
+ pub fn set_delta_tick<W>(&mut self, warn: &mut W, tick: i32)
+ -> Result<(), UnknownSnap>
+ where W: Warn<WeirdNegativeDeltaTick>,
+ {
+ if tick < 0 {
+ if tick != -1 {
+ warn.warn(WeirdNegativeDeltaTick);
+ }
+ self.delta_tick = None;
+ return Ok(());
+ }
+ if let Some(i) = self.snaps.iter().position(|s| s.tick < tick) {
+ let self_free = &mut self.free;
+ // FIXME: Replace with something like `exhaust`.
+ self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count();
+ }
+ if !self.snaps.back().map(|s| s.tick == tick).unwrap_or(false) {
+ self.delta_tick = None;
+ return Err(UnknownSnap);
+ }
+ self.delta_tick = Some(tick);
+ Ok(())
+ }
+ pub fn delta_tick(&self) -> Option<i32> {
+ self.delta_tick
+ }
+ pub fn add_snap(&mut self, tick: i32, snap: Snap) -> &Delta {
+ self.snaps.push_front(StoredSnap {
+ snap: snap,
+ tick: tick,
+ });
+ let tmp;
+ let delta_snap = if self.delta_tick.is_some() {
+ &self.snaps.back().unwrap().snap
+ } else {
+ tmp = Snap::empty();
+ &tmp
+ };
+ self.delta.create(delta_snap, &self.snaps.front().unwrap().snap);
+ &self.delta
+ }
+}