This commit is contained in:
xeon 2025-03-28 20:47:30 +03:00
parent 393967e308
commit 24abc716d8
31 changed files with 2815 additions and 2 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[build]
target = "armv7-linux-androideabi"

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

951
Cargo.lock generated Normal file
View file

@ -0,0 +1,951 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android_log-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "bindgen"
version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[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 = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "libproc"
version = "0.14.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb"
dependencies = [
"bindgen",
"errno",
"libc",
]
[[package]]
name = "libserver"
version = "0.0.1"
dependencies = [
"jni",
"libc",
"proc-maps",
"rand",
"rbase64",
"rusqlite",
"tracing",
"tracing-android",
"tracing-subscriber",
]
[[package]]
name = "libsqlite3-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "mach2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mimalloc"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "once_cell"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proc-maps"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db44c5aa60e193a25fcd93bb9ed27423827e8f118897866f946e2cf936c44fb"
dependencies = [
"anyhow",
"bindgen",
"libc",
"libproc",
"mach2",
"winapi",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha",
"rand_core",
"zerocopy",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "rbase64"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b133fdd52a7cbb7619c86d93c8a34ea6e056462f901e08f6cbb6c9baf138b13"
dependencies = [
"mimalloc",
"rayon",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rusqlite"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-android"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12612be8f868a09c0ceae7113ff26afe79d81a24473a393cb9120ece162e86c0"
dependencies = [
"android_log-sys",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[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-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.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[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.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "zerocopy"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[workspace]
members = ["libserver"]
resolver = "2"
[workspace.package]
version = "0.0.1"
edition = "2024"
[workspace.dependencies]
jni = "0.21.1"
tracing = "0.1.41"
tracing-android = "0.2.0"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
rand = "0.9"
rusqlite = { version = "0.34.0", features = ["bundled"] }
rbase64 = "2.0.3"
proc-maps = "0.4.0"
libc = "0.2.171"

View file

@ -1,3 +1,49 @@
# Magic
# magic-rs
Experimental Clash of Clans server emulator on top of libg.so (v8.67.8)
![Screenshot](screenshot.jpg)
Experimental Clash of Clans server on top of libg.so
# Why?
Supercell's games have their game logic implementation included in both client and server, for independent execution (the `Logic*` family of classes is shared). However, to implement a feature-complete server emulator, you should rewrite the logic entirely. This experimental way is to use logic that is retained in the game, basically turning client into a server.
# Current features
- Full home state emulation
- NPC attacks
- Player progress saving
# Implementation
Server side code is written in Rust. We provide idiomatic bindings for the structures/functions from `libg.so`. Player data is being saved inside application data in form of SQLite database (for sake of simplicity).
# Getting started
#### NOTE: you have to use a device with support of armeabi-v7a binaries
### a) Using pre-built apk files
Navigate to the [Releases](https://git.xeondev.com/Supercell/Magic/releases) page and download both Server and Client APK files (by default, they're targeted to the `127.0.0.1:9339` endpoint). Next, install both of them. Open the server application first (it should stay with black screen), then leave it running in the background. Open the client and play!
### b) Building from sources
#### Requirements:
- [Rust 1.85+](https://www.rust-lang.org/tools/install)
- [Android NDK](https://developer.android.com/ndk/downloads)
- [cargo-ndk](https://docs.rs/crate/cargo-ndk/3.5.4)
#### Preparing the toolchain
- Install android armv7 target via rustup:
```sh
rustup target add armv7-linux-androideabi
```
- Install cargo-ndk extension:
```sh
cargo install cargo-ndk
```
##### NOTE: make sure you have configured the `ANDROID_NDK_HOME` environment variable before invoking build command.
#### Compiling server library
```sh
git clone https://git.xeondev.com/Supercell/Magic
cd Magic
cargo ndk -t armeabi-v7a build --release
```
### Community
[Our Discord Server](https://discord.gg/reversedrooms) is open for everyone who's interested in our projects!
### Support
Your support for this project is greatly appreciated! If you'd like to contribute, feel free to send a tip [via Boosty](https://boosty.to/xeondev/donate)!

22
libserver/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "libserver"
version.workspace = true
edition.workspace = true
[lib]
name = "server"
crate-type = ["cdylib"]
[dependencies]
jni.workspace = true
tracing.workspace = true
tracing-android.workspace = true
tracing-subscriber.workspace = true
rand.workspace = true
rusqlite.workspace = true
rbase64.workspace = true
proc-maps.workspace = true
libc.workspace = true

View file

@ -0,0 +1,15 @@
#[repr(C)]
pub struct LogicArrayList<T> {
pub data: *const T,
pub _capacity: usize,
pub count: usize,
}
impl<T> LogicArrayList<T>
where
T: Sized,
{
pub fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.data, self.count) }
}
}

View file

@ -0,0 +1,53 @@
use crate::{import, malloc};
pub struct ByteStream(pub *const u8);
impl ByteStream {
pub fn new(initial_capacity: usize) -> Self {
import!(byte_stream_ctor(ptr: *const u8, initial_capacity: i32) -> () = 0x1A0DA8 + 1);
let instance = malloc(40);
byte_stream_ctor(instance, initial_capacity as i32);
Self(instance)
}
pub fn get_byte_array(&self) -> &[u8] {
unsafe {
let byte_array_ptr = *(self.0.wrapping_add(28) as *const *const u8);
let length = self.get_length();
std::slice::from_raw_parts(byte_array_ptr, length as usize)
}
}
pub fn get_length(&self) -> i32 {
unsafe {
let offset = *(self.0.wrapping_add(16) as *const i32);
let length = *(self.0.wrapping_add(20) as *const i32);
std::cmp::max(offset, length)
}
}
pub fn reset_offset(&mut self) {
unsafe { *(self.0.wrapping_add(16) as *mut i32) = 0 }
}
}
impl<T> From<T> for ByteStream
where
T: AsRef<[u8]>,
{
fn from(value: T) -> Self {
let value = value.as_ref();
let stream = ByteStream::new(value.len());
unsafe {
std::slice::from_raw_parts_mut(
*(stream.0.wrapping_add(28) as *const *mut u8),
value.len(),
)
.copy_from_slice(value);
*(stream.0.wrapping_add(20) as *mut i32) = value.len() as i32;
}
stream
}
}

124
libserver/src/database.rs Normal file
View file

@ -0,0 +1,124 @@
use rand::distr::{Alphanumeric, SampleString};
use rusqlite::{params, Connection, Result};
use tracing::error;
use crate::{
byte_stream::ByteStream, logic::avatar::LogicClientAvatar, math::LogicLong,
resources::ResourceManager, sc_string::StringBuilder,
};
pub struct DatabaseConnection(Connection);
pub struct PlayerSaveData {
pub id: LogicLong,
pub pass_token: String,
pub home_json: String,
pub client_avatar_blob: String,
}
impl DatabaseConnection {
pub fn connect(path: &str) -> Result<Self> {
const INIT_QUERY: &str = r#"
CREATE TABLE IF NOT EXISTS t_player_data (
id INTEGER PRIMARY KEY,
pass_token TEXT NOT NULL,
home_json TEXT NOT NULL,
client_avatar_blob TEXT NOT NULL,
score INTEGER NOT NULL
)
"#;
let connection = rusqlite::Connection::open(path)?;
connection.execute(INIT_QUERY, [])?;
Ok(Self(connection))
}
pub fn fetch_or_create_player(&self, id: &LogicLong) -> Result<Option<PlayerSaveData>> {
if id.is_zero() {
Ok(Some(self.create_new_player_data()?))
} else {
self.load_existing_player_data(id)
}
}
pub fn save_player_data(
&self,
id: &LogicLong,
home_json: &str,
avatar: &LogicClientAvatar,
) -> Result<()> {
const UPDATE_QUERY: &str =
r#"UPDATE t_player_data SET home_json = ?1, client_avatar_blob = ?2 WHERE id = ?3"#;
let mut byte_stream = ByteStream::new(10);
avatar.encode(&mut byte_stream);
let client_avatar_blob = rbase64::encode(byte_stream.get_byte_array());
self.0.execute(
UPDATE_QUERY,
params![home_json, &client_avatar_blob, id.lower_int],
)?;
Ok(())
}
fn create_new_player_data(&self) -> Result<PlayerSaveData> {
const INSERT_QUERY: &str = r#"
INSERT INTO t_player_data (pass_token, home_json, client_avatar_blob, score)
values (?1, ?2, ?3, ?4) RETURNING *
"#;
let pass_token = Alphanumeric.sample_string(&mut rand::rng(), 40);
let mut sb = StringBuilder::new();
ResourceManager::get_json("level/starting_home.json").write_to_string(&mut sb);
let home_json = sb.to_string();
let logic_client_avatar = LogicClientAvatar::get_default_avatar();
let mut byte_stream = ByteStream::new(10);
logic_client_avatar.encode(&mut byte_stream);
let client_avatar_blob = rbase64::encode(byte_stream.get_byte_array());
let id: i32 = self
.0
.prepare(INSERT_QUERY)
.inspect_err(|err| {
error!("db::prepare `insert into t_player_data` failed: {err}");
})?
.query_map(
params![&pass_token, &home_json, &client_avatar_blob, 0],
|row| row.get(0),
)?
.next()
.expect("query didn't return inserted data")?;
Ok(PlayerSaveData {
id: LogicLong::new(0, id),
pass_token,
home_json,
client_avatar_blob,
})
}
fn load_existing_player_data(&self, id: &LogicLong) -> Result<Option<PlayerSaveData>> {
const SELECT_QUERY: &str = r#"SELECT * FROM t_player_data WHERE id = (?1)"#;
self.0
.prepare(SELECT_QUERY)
.inspect_err(|err| {
error!("db::prepare `select from t_player_data` failed: {err}");
})?
.query_map(params![id.lower_int], |row| {
Ok(PlayerSaveData {
id: LogicLong::new(0, row.get(0)?),
pass_token: row.get(1)?,
home_json: row.get(2)?,
client_avatar_blob: row.get(3)?,
})
})?
.into_iter()
.next()
.transpose()
}
}

55
libserver/src/ffi_util.rs Normal file
View file

@ -0,0 +1,55 @@
use proc_maps::Pid;
use std::{ffi::c_void, sync::LazyLock};
pub static LIBG_BASE: LazyLock<usize> = LazyLock::new(|| get_module_base("libg.so").unwrap());
macro_rules! import {
($name:ident($($arg_name:ident: $arg_type:ty),*) -> $ret_type:ty = $rva:expr) => {
pub fn $name($($arg_name: $arg_type,)*) -> $ret_type {
unsafe {
type FuncType = unsafe extern "C" fn($($arg_type,)*) -> $ret_type;
::std::mem::transmute::<usize, FuncType>(*crate::ffi_util::LIBG_BASE + $rva)($($arg_name,)*)
}
}
};
}
pub fn disable_event_tracker() {
// Causes crashes in logic functions due to being not initialized
// useless SC analytics.
const EVENT_TRACKER_FUNCTIONS: &[i32] = &[0x14BCC0, 0x14BA1C, 0x14BB4C, 0x14BA88, 0x1A39A0];
const TRACK_FUNCTIONS: &[i32] = &[0x1A3E58, 0x14BE64, 0x14BC58];
unsafe {
for &addr in EVENT_TRACKER_FUNCTIONS.iter().chain(TRACK_FUNCTIONS) {
let page_size = libc::sysconf(libc::_SC_PAGE_SIZE);
let addr = LIBG_BASE.wrapping_add(addr as usize);
libc::mprotect(
((addr as i32) & !(page_size - 1)) as *mut c_void,
page_size as usize,
libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
);
std::slice::from_raw_parts_mut(addr as *mut u8, 2).copy_from_slice(&[0x70, 0x47]);
}
}
}
pub(crate) use import;
pub fn get_module_base(shared_object_name: &str) -> Option<usize> {
const ELF_MAGIC: u32 = 0x464C457F;
proc_maps::get_process_maps(std::process::id() as Pid)
.ok()?
.into_iter()
.filter(|range| {
range
.filename()
.map(|p| p.to_string_lossy().ends_with(shared_object_name))
.unwrap_or(false)
})
.find(|range| unsafe { *(range.start() as *const u32) } == ELF_MAGIC)
.map(|range| range.start())
}

37
libserver/src/jni_util.rs Normal file
View file

@ -0,0 +1,37 @@
use jni::JNIEnv;
pub fn get_package_name(mut env: JNIEnv) -> String {
let activity_thread = env.find_class("android/app/ActivityThread").unwrap();
let current_application = env
.call_static_method(
&activity_thread,
"currentApplication",
"()Landroid/app/Application;",
&[],
)
.unwrap()
.l()
.unwrap();
let context = env
.call_method(
current_application,
"getApplicationContext",
"()Landroid/content/Context;",
&[],
)
.unwrap()
.l()
.unwrap();
let package_name = env
.call_method(context, "getPackageName", "()Ljava/lang/String;", &[])
.unwrap()
.l()
.unwrap();
env.get_string(&package_name.into())
.unwrap()
.to_string_lossy()
.to_string()
}

452
libserver/src/lib.rs Normal file
View file

@ -0,0 +1,452 @@
use std::{
ffi::c_void,
net::{SocketAddr, TcpListener},
os::fd::IntoRawFd,
sync::{Arc, LazyLock, Mutex},
thread,
};
use byte_stream::ByteStream;
use database::DatabaseConnection;
use ffi_util::import;
use logic::avatar::*;
use logic::home::LogicClientHome;
use logic::json::LogicJSONNode;
use logic::mode::LogicGameMode;
use math::LogicLong;
use network::PiranhaMessage;
use rand::RngCore;
use resources::ResourceManager;
use sc_string::StringBuilder;
use tracing::{error, info, warn};
mod array_list;
mod byte_stream;
mod database;
mod ffi_util;
mod jni_util;
mod logic;
mod math;
mod message;
mod network;
mod resources;
mod sc_string;
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub unsafe extern "system" fn JNI_OnLoad(vm: jni::JavaVM, _: *mut c_void) -> jni::sys::jint {
init_tracing();
let env = vm.get_env().unwrap();
let package_name = jni_util::get_package_name(env);
info!("OnLoad()");
info!("PackageName: {package_name}");
thread::spawn(move || {
server_main(ServerConfig {
database_path: format!("/data/data/{package_name}/magic.db"),
})
});
jni::sys::JNI_VERSION_1_6
}
struct ServerConfig {
pub database_path: String,
}
import!(malloc(amount: usize) -> *const u8 = 0x56A20);
import!(free(ptr: *const u8) -> () = 0x56A2C);
fn server_main(config: ServerConfig) {
const TCP_ADDR: &str = "127.0.0.1:9339";
info!("starting server...");
ffi_util::disable_event_tracker();
resources::init();
info!("successfully initialized resources");
let db = DatabaseConnection::connect(&config.database_path).unwrap_or_else(|err| {
error!("DatabaseConnection::connect failed: {err}");
panic!();
});
let db = Arc::new(Mutex::new(db));
let listener = TcpListener::bind(TCP_ADDR).unwrap();
info!("server is listening at {TCP_ADDR}");
while let Ok((stream, addr)) = listener.accept() {
info!("new connection from {addr}");
let fd = stream.into_raw_fd();
let db = Arc::clone(&db);
thread::spawn(move || receive_loop(fd, addr, db));
}
}
fn receive_loop(fd: i32, addr: SocketAddr, db: Arc<Mutex<DatabaseConnection>>) {
use network::{LogicMagicMessageFactory, Messaging, RC4Encrypter};
static MESSAGE_FACTORY: LazyLock<LogicMagicMessageFactory> =
LazyLock::new(|| LogicMagicMessageFactory::new());
let mut messaging = Messaging::new(fd);
messaging.set_message_factory(&*MESSAGE_FACTORY);
messaging.set_encrypters(
RC4Encrypter::new(LogicMagicMessageFactory::RC4_KEY, "nonce"),
RC4Encrypter::new(LogicMagicMessageFactory::RC4_KEY, "nonce"),
);
let mut session = PlayerSession {
messaging,
account_id: LogicLong::new(0, 0),
logic_game_mode: None,
saved_home_json: None,
};
while session.messaging.get_connection().is_connected {
session.messaging.on_receive();
while let Some(message) = session.messaging.next_message() {
handle_message(&mut session, db.as_ref(), message);
}
}
info!("client from {addr} disconnected");
}
struct PlayerSession {
pub messaging: network::Messaging,
pub account_id: LogicLong,
pub logic_game_mode: Option<LogicGameMode>,
pub saved_home_json: Option<String>,
}
fn handle_message(
session: &mut PlayerSession,
db: &Mutex<DatabaseConnection>,
message: PiranhaMessage,
) {
match message.get_message_type() {
10101 => handle_login_message(session, db, message),
10108 => handle_keep_alive_message(session, message),
10212 => handle_change_avatar_name_message(session, message),
14101 => handle_go_home_message(session, message),
14102 => handle_end_client_turn_message(session, db, message),
14134 => handle_attack_npc_message(session, message),
unhandled => warn!("unhandled message: {unhandled}"),
}
session.messaging.on_wakeup();
}
fn handle_login_message(
session: &mut PlayerSession,
db: &Mutex<DatabaseConnection>,
message: PiranhaMessage,
) {
use message::{ExtendedSetEncryptionMessage, LoginMessage, LoginOkMessage, OwnHomeDataMessage};
use network::{LogicMagicMessageFactory, RC4Encrypter};
let login_message = LoginMessage(message);
info!(
"LoginMessage received, account_id: {}, pass_token: {:?}",
login_message.get_account_id(),
login_message.get_pass_token()
);
let Ok(Some(player_data)) = db
.lock()
.unwrap()
.fetch_or_create_player(&login_message.get_account_id())
else {
warn!(
"Login: player with id {} was not found in the database",
login_message.get_account_id()
);
return;
};
if !login_message.get_account_id().is_zero() {
let Some(pass_token) = login_message.get_pass_token() else {
error!(
"Login: received null pass token with non-zero account id: {}",
login_message.get_account_id()
);
return;
};
if pass_token.to_string() != player_data.pass_token {
warn!(
"Login: pass token mismatch, account id: {}",
login_message.get_account_id()
);
return;
}
}
let mut set_encryption_message = ExtendedSetEncryptionMessage::new();
let mut nonce = [0u8; 64];
rand::rng().fill_bytes(&mut nonce);
set_encryption_message.set_nonce(&nonce);
set_encryption_message.set_scrambler_method(1);
session.messaging.send(set_encryption_message.0);
session.messaging.on_wakeup();
network::Messaging::scramble_nonce_using_mersenne_twister(
login_message.get_scrambler_seed(),
&mut nonce,
);
let encrypter = RC4Encrypter::new_with_nonce_bytes(LogicMagicMessageFactory::RC4_KEY, &nonce);
let decrypter = RC4Encrypter::new_with_nonce_bytes(LogicMagicMessageFactory::RC4_KEY, &nonce);
session.messaging.set_encrypters(encrypter, decrypter);
let mut login_ok_message = LoginOkMessage::new();
login_ok_message.set_account_id(player_data.id.clone());
login_ok_message.set_home_id(player_data.id.clone());
login_ok_message.set_pass_token(&player_data.pass_token);
login_ok_message.set_server_major_version(8);
login_ok_message.set_server_build(67);
login_ok_message.set_content_version(0);
login_ok_message.set_server_environment("dev");
let mut own_home_data_message = OwnHomeDataMessage::new();
let mut logic_client_home = LogicClientHome::new();
logic_client_home.set_home_json(&player_data.home_json);
let mut logic_client_avatar = LogicClientAvatar::new();
let data = rbase64::decode(&player_data.client_avatar_blob).unwrap();
let mut byte_stream = ByteStream::from(&data);
logic_client_avatar.decode(&mut byte_stream);
let mut logic_game_mode = LogicGameMode::new();
logic_game_mode.load_home_state(&logic_client_home, &logic_client_avatar, 0);
own_home_data_message.set_logic_client_home({
let mut logic_client_home = LogicClientHome::new();
logic_client_home.set_home_json(&player_data.home_json);
logic_client_home
});
own_home_data_message.set_logic_client_avatar(logic_game_mode.get_cloned_home_owner().unwrap());
session.account_id = player_data.id;
session.logic_game_mode = Some(logic_game_mode);
session.saved_home_json = Some(player_data.home_json);
session.messaging.send(login_ok_message.0);
session.messaging.send(own_home_data_message.0);
info!("successfully logged in");
}
fn handle_keep_alive_message(session: &mut PlayerSession, _message: PiranhaMessage) {
session
.messaging
.send(message::KeepAliveServerMessage::new().0);
}
fn handle_go_home_message(session: &mut PlayerSession, _message: PiranhaMessage) {
use message::OwnHomeDataMessage;
let Some(logic_game_mode) = session.logic_game_mode.as_mut() else {
error!("received GoHomeMessage while LogicGameMode is NULL!");
return;
};
if logic_game_mode.get_state() == 1 {
error!("received GoHomeMessage being already in home state!");
return;
}
let Some(home_json) = session.saved_home_json.as_ref() else {
error!("received GoHomeMessage while saved_home_json is NULL!");
return;
};
let mut own_home_data_message = OwnHomeDataMessage::new();
let mut logic_client_home = LogicClientHome::new();
logic_client_home.set_home_json(home_json);
let logic_client_avatar = logic_game_mode.get_cloned_visitor().unwrap();
let mut logic_game_mode = LogicGameMode::new();
logic_game_mode.load_home_state(&logic_client_home, &logic_client_avatar, 0);
own_home_data_message.set_logic_client_home({
let mut logic_client_home = LogicClientHome::new();
logic_client_home.set_home_json(home_json);
logic_client_home
});
own_home_data_message.set_logic_client_avatar(logic_game_mode.get_cloned_home_owner().unwrap());
session.logic_game_mode = Some(logic_game_mode);
session.messaging.send(own_home_data_message.0);
}
fn handle_end_client_turn_message(
session: &mut PlayerSession,
db: &Mutex<DatabaseConnection>,
message: PiranhaMessage,
) {
use message::{EndClientTurnMessage, OutOfSyncMessage};
let message = EndClientTurnMessage(message);
let Some(logic_game_mode) = session.logic_game_mode.as_mut() else {
error!("received EndClientTurnMessage while LogicGameMode is NULL!");
return;
};
info!(
"EndClientTurnMessage received: sub_tick: {}, checksum: {}",
message.get_sub_tick(),
message.get_checksum()
);
let client_sub_tick = message.get_sub_tick();
while logic_game_mode.get_level().get_time().sub_tick < client_sub_tick {
if let Some(commands) = message.get_commands() {
let cur_sub_tick = logic_game_mode.get_level().get_time().sub_tick;
for command in commands.as_slice().iter() {
if command.get_execute_sub_tick() == cur_sub_tick {
info!(
"received command: {}, exec sub tick: {}",
command.get_command_type(),
command.get_execute_sub_tick()
);
logic_game_mode.get_command_manager().add_command(command);
}
}
}
logic_game_mode.update_one_sub_tick();
}
let mut debug_json = LogicJSONNode::new_json_object();
let checksum = logic_game_mode.calculate_checksum(Some(&mut debug_json), false);
if checksum != message.get_checksum() {
error!("Client and server are out of sync! sub_tick: {}, server checksum: {}, client checksum: {}", message.get_sub_tick(), checksum, message.get_checksum());
let mut sb = StringBuilder::new();
debug_json.write_to_string(&mut sb);
info!("{}", sb.to_string());
let mut out_of_sync_message = OutOfSyncMessage::new();
out_of_sync_message.set_server_checksum(checksum);
out_of_sync_message.set_client_checksum(message.get_checksum());
out_of_sync_message.set_sub_tick(message.get_sub_tick());
session.messaging.send(out_of_sync_message.0);
}
if logic_game_mode.get_state() == 1 {
let mut string_builder = StringBuilder::new();
let mut home_json_object = LogicJSONNode::new_json_object();
logic_game_mode.save_to_json(&mut home_json_object);
home_json_object.write_to_string(&mut string_builder);
let home_json = string_builder.to_string();
if let Err(err) = db.lock().unwrap().save_player_data(
&session.account_id,
&home_json,
&logic_game_mode.get_cloned_home_owner().unwrap(),
) {
error!("failed to save player data: {err}");
}
session.saved_home_json = Some(home_json);
}
}
fn handle_attack_npc_message(session: &mut PlayerSession, message: PiranhaMessage) {
use message::{AttackNpcMessage, NpcDataMessage};
let message = AttackNpcMessage(message);
let Some(logic_game_mode) = session.logic_game_mode.as_mut() else {
error!("received AttackNpcMessage while LogicGameMode is NULL!");
return;
};
let Some(home_owner_avatar) = logic_game_mode.get_level().get_home_owner_avatar() else {
error!("received AttackNpcMessage while home_owner_avatar is NULL!");
return;
};
let mut string_builder = StringBuilder::new();
ResourceManager::get_json(
&message
.get_npc_data()
.get_level_json_file_name()
.to_string(),
)
.write_to_string(&mut string_builder);
let mut logic_client_home = LogicClientHome::new();
logic_client_home.set_home_json(&string_builder.to_string());
let mut logic_npc_avatar = LogicNpcAvatar::new();
logic_npc_avatar.set_npc_data(&message.get_npc_data());
let mut logic_game_mode = LogicGameMode::new();
logic_game_mode.load_npc_attack_state(
&logic_client_home,
&logic_npc_avatar,
&home_owner_avatar,
0,
);
let mut npc_data_message = NpcDataMessage::new();
npc_data_message.set_level_json(&string_builder.to_string());
npc_data_message.set_logic_npc_avatar(&logic_game_mode.get_cloned_home_owner().unwrap());
npc_data_message.set_logic_client_avatar(&logic_game_mode.get_cloned_visitor().unwrap());
session.logic_game_mode = Some(logic_game_mode);
session.messaging.send(npc_data_message.0);
}
fn handle_change_avatar_name_message(session: &mut PlayerSession, message: PiranhaMessage) {
use logic::command::LogicChangeAvatarNameCommand;
use message::{AvailableServerCommandMessage, ChangeAvatarNameMessage};
let message = ChangeAvatarNameMessage(message);
let mut logic_change_avatar_name_command = LogicChangeAvatarNameCommand::new();
logic_change_avatar_name_command.set_avatar_name(&message.get_avatar_name());
logic_change_avatar_name_command.set_name_change_state(1);
let mut available_server_command_message = AvailableServerCommandMessage::new();
available_server_command_message.set_server_command(&logic_change_avatar_name_command.0);
session.messaging.send(available_server_command_message.0);
}
fn init_tracing() {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_android::layer("MAGIC-SERVER").unwrap())
.with(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
}

View file

@ -0,0 +1,112 @@
use crate::{byte_stream::ByteStream, import, malloc};
use super::data::LogicNpcData;
pub trait LogicAvatar: Sized {
fn new_from_ptr(ptr: *const u8) -> Self;
fn new() -> Self;
fn decode(&mut self, stream: &mut ByteStream);
fn encode(&self, stream: &mut ByteStream);
}
#[repr(transparent)]
pub struct LogicClientAvatar(pub *const u8);
impl LogicClientAvatar {
pub fn new() -> Self {
import!(logic_client_avatar_ctor(ptr: *const u8) -> () = 0x1164A4 + 1);
let instance = malloc(296);
logic_client_avatar_ctor(instance);
Self(instance)
}
pub fn get_default_avatar() -> Self {
import!(logic_avatar_set_resource_count(ptr: *const u8, data: *const u8, count: i32) -> () = 0x114E7C + 1);
import!(logic_data_tables_get_gold_data() -> *const u8 = 0x12D4F8 + 1);
import!(logic_data_tables_get_elixir_data() -> *const u8 = 0x12D510 + 1);
let avatar = Self::new();
logic_avatar_set_resource_count(avatar.0, logic_data_tables_get_gold_data(), 750);
logic_avatar_set_resource_count(avatar.0, logic_data_tables_get_elixir_data(), 750);
unsafe {
*(avatar.0.wrapping_add(96) as *mut i32) = 1; // level
*(avatar.0.wrapping_add(208) as *mut i32) = 100_000_000; // diamonds
}
avatar
}
pub fn decode(&mut self, stream: &mut ByteStream) {
import!(logic_client_avatar_decode(ptr: *const u8, s: *const u8) -> () = 0x1174AC + 1);
logic_client_avatar_decode(self.0, stream.0);
}
pub fn encode(&self, stream: &mut ByteStream) {
import!(logic_client_avatar_encode(ptr: *const u8, s: *const u8) -> () = 0x115D24 + 1);
logic_client_avatar_encode(self.0, stream.0);
}
}
#[repr(transparent)]
pub struct LogicNpcAvatar(pub *const u8);
impl LogicNpcAvatar {
pub fn new() -> Self {
import!(logic_npc_avatar_ctor(ptr: *const u8) -> () = 0x117FE4 + 1);
let instance = malloc(112);
logic_npc_avatar_ctor(instance);
Self(instance)
}
pub fn set_npc_data(&mut self, data: &LogicNpcData) {
import!(logic_npc_avatar_set_npc_data(ptr: *const u8, data: *const u8) -> () = 0x118008 + 1);
logic_npc_avatar_set_npc_data(self.0, data.0);
}
pub fn decode(&mut self, stream: &mut ByteStream) {
import!(logic_npc_avatar_decode(ptr: *const u8, s: *const u8) -> () = 0x1180E4 + 1);
logic_npc_avatar_decode(self.0, stream.0);
}
pub fn encode(&self, stream: &mut ByteStream) {
import!(logic_npc_avatar_encode(ptr: *const u8, s: *const u8) -> () = 0x117FB8 + 1);
logic_npc_avatar_encode(self.0, stream.0);
}
}
impl LogicAvatar for LogicClientAvatar {
fn new_from_ptr(ptr: *const u8) -> Self {
Self(ptr)
}
fn new() -> Self {
Self::new()
}
fn decode(&mut self, stream: &mut ByteStream) {
self.decode(stream);
}
fn encode(&self, stream: &mut ByteStream) {
self.encode(stream);
}
}
impl LogicAvatar for LogicNpcAvatar {
fn new_from_ptr(ptr: *const u8) -> Self {
Self(ptr)
}
fn new() -> Self {
Self::new()
}
fn decode(&mut self, stream: &mut ByteStream) {
self.decode(stream);
}
fn encode(&self, stream: &mut ByteStream) {
self.encode(stream);
}
}

View file

@ -0,0 +1,53 @@
use crate::{import, malloc, sc_string::ScString};
#[repr(transparent)]
pub struct LogicCommand(pub *const u8);
impl LogicCommand {
pub fn get_command_type(&self) -> i32 {
let get_command_type = unsafe {
std::mem::transmute::<_, extern "C" fn(*const u8) -> i32>(
*((*(self.0 as *const usize) + 16) as *const usize),
)
};
get_command_type(self.0)
}
pub fn get_execute_sub_tick(&self) -> i32 {
unsafe { *(self.0.wrapping_add(4) as *const i32) }
}
}
#[repr(transparent)]
pub struct LogicCommandManager(pub *const u8);
impl LogicCommandManager {
pub fn add_command(&self, command: &LogicCommand) {
import!(logic_command_manager_add_command(ptr: *const u8, command: *const u8) -> () = 0x11CA20 + 1);
logic_command_manager_add_command(self.0, command.0);
}
}
pub struct LogicChangeAvatarNameCommand(pub LogicCommand);
impl LogicChangeAvatarNameCommand {
pub fn new() -> Self {
import!(logic_change_avatar_name_command_ctor(ptr: *const u8) -> () = 0x14F7D8 + 1);
let instance = malloc(20);
logic_change_avatar_name_command_ctor(instance);
Self(LogicCommand(instance))
}
pub fn set_avatar_name(&mut self, name: &str) {
unsafe {
*(self.0 .0.wrapping_add(12) as *mut usize) = ScString::from(name).0 as usize;
}
}
pub fn set_name_change_state(&mut self, value: i32) {
unsafe {
*(self.0 .0.wrapping_add(16) as *mut i32) = value;
}
}
}

View file

@ -0,0 +1,3 @@
mod npc;
pub use npc::LogicNpcData;

View file

@ -0,0 +1,9 @@
use crate::sc_string::ScString;
pub struct LogicNpcData(pub *const u8);
impl LogicNpcData {
pub fn get_level_json_file_name(&self) -> ScString {
ScString(self.0.wrapping_add(144) as *const u8)
}
}

View file

@ -0,0 +1,20 @@
use crate::{import, malloc, sc_string::ScString};
#[repr(transparent)]
pub struct LogicClientHome(pub *const u8);
impl LogicClientHome {
pub fn new() -> Self {
import!(logic_client_home_ctor(ptr: *const u8) -> () = 0x144900 + 1);
let instance = malloc(48);
logic_client_home_ctor(instance);
Self(instance)
}
pub fn set_home_json(&mut self, home_json: &str) {
unsafe {
*(self.0.wrapping_add(36) as *mut usize) = ScString::from(home_json).0 as usize;
}
}
}

View file

@ -0,0 +1,22 @@
use crate::{import, malloc, sc_string::StringBuilder};
#[repr(transparent)]
pub struct LogicJSONNode(pub *const u8);
impl LogicJSONNode {
pub fn new_json_object() -> Self {
import!(logic_json_object_ctor(ptr: *const u8) -> () = 0x1A1DF0 + 1);
let instance = malloc(28);
logic_json_object_ctor(instance);
Self(instance)
}
pub fn write_to_string(&self, string_builder: &mut StringBuilder) {
let write_to_string = unsafe {
std::mem::transmute::<_, extern "C" fn(*const u8, *const u8)>(
*((*(self.0 as *const usize) + 16) as *const usize),
)
};
write_to_string(self.0, string_builder.0);
}
}

View file

@ -0,0 +1,31 @@
use crate::import;
use super::{avatar::LogicAvatar, json::LogicJSONNode, time::LogicTime};
#[repr(transparent)]
pub struct LogicLevel(pub *const u8);
impl LogicLevel {
pub fn get_time(&self) -> &LogicTime {
unsafe { &*(self.0.wrapping_add(88) as *const LogicTime) }
}
pub fn get_home_owner_avatar<T: LogicAvatar>(&self) -> Option<T> {
unsafe {
let ptr = *(self.0.wrapping_add(76) as *const *const u8);
(!ptr.is_null()).then_some(T::new_from_ptr(ptr))
}
}
pub fn get_visitor_avatar<T: LogicAvatar>(&self) -> Option<T> {
unsafe {
let ptr = *(self.0.wrapping_add(80) as *const *const u8);
(!ptr.is_null()).then_some(T::new_from_ptr(ptr))
}
}
pub fn save_to_json(&self, json: &mut LogicJSONNode) {
import!(logic_level_save_to_json(ptr: *const u8, json: *const u8) -> () = 0x147A48 + 1);
logic_level_save_to_json(self.0, json.0);
}
}

View file

@ -0,0 +1,8 @@
pub mod avatar;
pub mod command;
pub mod data;
pub mod home;
pub mod json;
pub mod level;
pub mod mode;
pub mod time;

116
libserver/src/logic/mode.rs Normal file
View file

@ -0,0 +1,116 @@
use crate::{byte_stream::ByteStream, import, malloc};
use super::{
avatar::{LogicAvatar, LogicClientAvatar, LogicNpcAvatar},
command::LogicCommandManager,
home::LogicClientHome,
json::LogicJSONNode,
level::LogicLevel,
};
#[repr(transparent)]
pub struct LogicGameMode(pub *const u8);
impl LogicGameMode {
pub fn new() -> Self {
import!(logic_game_mode_ctor(ptr: *const u8) -> () = 0x149E0C + 1);
let instance = malloc(68);
logic_game_mode_ctor(instance);
Self(instance)
}
pub fn load_home_state(
&mut self,
logic_client_home: &LogicClientHome,
logic_client_avatar: &LogicClientAvatar,
seconds_since_last_save: i32,
) {
import!(logic_game_mode_load_home_state(lgm: *const u8, lch: *const u8, lca: *const u8, ssls: i32, a5: i32, a6: i32, a7: i32) -> () = 0x14ACCA + 1);
logic_game_mode_load_home_state(
self.0,
logic_client_home.0,
logic_client_avatar.0,
seconds_since_last_save,
0,
-1,
-1,
);
}
pub fn load_npc_attack_state(
&mut self,
logic_client_home: &LogicClientHome,
logic_npc_avatar: &LogicNpcAvatar,
logic_client_avatar: &LogicClientAvatar,
seconds_since_last_save: i32,
) {
import!(logic_game_mode_load_npc_attack_state(lgm: *const u8, lch: *const u8, lna: *const u8, lca: *const u8, ssls: i32) -> () = 0x14A1D8 + 1);
logic_game_mode_load_npc_attack_state(
self.0,
logic_client_home.0,
logic_npc_avatar.0,
logic_client_avatar.0,
seconds_since_last_save,
);
}
pub fn get_cloned_home_owner<T: LogicAvatar>(&self) -> Option<T> {
let avatar = self.get_level().get_home_owner_avatar::<T>()?;
let mut stream = ByteStream::new(10);
LogicAvatar::encode(&avatar, &mut stream);
stream.reset_offset();
let mut cloned_avatar = T::new();
LogicAvatar::decode(&mut cloned_avatar, &mut stream);
Some(cloned_avatar)
}
pub fn get_cloned_visitor<T: LogicAvatar>(&self) -> Option<T> {
let avatar = self.get_level().get_visitor_avatar::<T>()?;
let mut stream = ByteStream::new(10);
LogicAvatar::encode(&avatar, &mut stream);
stream.reset_offset();
let mut cloned_avatar = T::new();
LogicAvatar::decode(&mut cloned_avatar, &mut stream);
Some(cloned_avatar)
}
pub fn update_one_sub_tick(&self) {
import!(logic_game_mode_update_one_sub_tick(ptr: *const u8) -> () = 0x14A8EC + 1);
logic_game_mode_update_one_sub_tick(self.0);
}
pub fn calculate_checksum(
&self,
debug_json: Option<&mut LogicJSONNode>,
include_game_objects: bool,
) -> i32 {
import!(logic_game_mode_calculate_checksum(ptr: *const u8, debug_json: *const u8, include_game_objects: bool) -> i32 = 0x14AB54 + 1);
logic_game_mode_calculate_checksum(
self.0,
debug_json.map(|ptr| ptr.0).unwrap_or(std::ptr::null()),
include_game_objects,
)
}
pub fn save_to_json(&self, json: &mut LogicJSONNode) {
self.get_level().save_to_json(json);
}
pub fn get_command_manager(&self) -> LogicCommandManager {
unsafe { LogicCommandManager(*(self.0.wrapping_add(20) as *const *const u8)) }
}
pub fn get_level(&self) -> LogicLevel {
unsafe { LogicLevel(*(self.0.wrapping_add(16) as *const *const u8)) }
}
pub fn get_state(&self) -> i32 {
unsafe { *(self.0 as *const i32) }
}
}

View file

@ -0,0 +1,5 @@
#[repr(C)]
pub struct LogicTime {
pub sub_tick: i32,
pub tick: i32,
}

39
libserver/src/math.rs Normal file
View file

@ -0,0 +1,39 @@
use std::fmt;
use crate::malloc;
#[repr(C)]
#[derive(PartialEq, Eq, Clone)]
pub struct LogicLong {
pub higher_int: i32,
pub lower_int: i32,
}
impl LogicLong {
pub fn new(higher_int: i32, lower_int: i32) -> Self {
Self {
higher_int,
lower_int,
}
}
pub fn to_heap(&self) -> *const LogicLong {
let ll = malloc(8);
unsafe {
*(ll as *mut i32) = self.higher_int;
*(ll.wrapping_add(4) as *mut i32) = self.lower_int;
}
ll as *const LogicLong
}
pub fn is_zero(&self) -> bool {
self.higher_int == 0 && self.lower_int == 0
}
}
impl fmt::Display for LogicLong {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LogicLong({},{})", self.higher_int, self.lower_int)
}
}

View file

@ -0,0 +1,112 @@
use crate::{import, malloc, math::LogicLong, network::PiranhaMessage, sc_string::ScString};
pub struct LoginMessage(pub PiranhaMessage);
impl LoginMessage {
pub fn get_account_id(&self) -> &LogicLong {
unsafe { &**(self.0 .0.wrapping_add(48) as *const *const LogicLong) }
}
pub fn get_pass_token(&self) -> Option<ScString> {
unsafe {
let strptr = *(self.0 .0.wrapping_add(52) as *const ScString);
(!strptr.0.is_null()).then_some(strptr)
}
}
pub fn get_scrambler_seed(&self) -> i32 {
unsafe { *(self.0 .0.wrapping_add(244) as *const i32) }
}
}
pub struct ExtendedSetEncryptionMessage(pub PiranhaMessage);
impl ExtendedSetEncryptionMessage {
pub fn new() -> Self {
import!(set_encryption_message_ctor(ptr: *const u8) -> () = 0x16F324 + 1);
let instance = malloc(60);
set_encryption_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_nonce(&mut self, nonce: &[u8]) {
let bytes = malloc(nonce.len());
unsafe {
std::slice::from_raw_parts_mut(bytes as *mut u8, nonce.len()).copy_from_slice(nonce);
*(self.0 .0.wrapping_add(48) as *mut *const u8) = bytes;
*(self.0 .0.wrapping_add(52) as *mut i32) = nonce.len() as i32;
}
}
pub fn set_scrambler_method(&mut self, method: i32) {
unsafe {
*(self.0 .0.wrapping_add(56) as *mut i32) = method;
}
}
}
pub struct LoginOkMessage(pub PiranhaMessage);
impl LoginOkMessage {
pub fn new() -> Self {
import!(login_ok_message_ctor(ptr: *const u8) -> () = 0x1629FC + 1);
let instance = malloc(124);
login_ok_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_account_id(&mut self, account_id: LogicLong) {
unsafe {
*(self.0 .0.wrapping_add(48) as *mut usize) = account_id.to_heap() as usize;
}
}
pub fn set_home_id(&mut self, home_id: LogicLong) {
unsafe {
*(self.0 .0.wrapping_add(52) as *mut usize) = home_id.to_heap() as usize;
}
}
pub fn set_pass_token(&mut self, pass_token: &str) {
unsafe {
*(self.0 .0.wrapping_add(56) as *mut usize) = ScString::from(pass_token).0 as usize;
}
}
pub fn set_server_major_version(&mut self, value: i32) {
unsafe {
*(self.0 .0.wrapping_add(76) as *mut i32) = value;
}
}
pub fn set_server_build(&mut self, value: i32) {
unsafe {
*(self.0 .0.wrapping_add(80) as *mut i32) = value;
}
}
pub fn set_content_version(&mut self, value: i32) {
unsafe {
*(self.0 .0.wrapping_add(84) as *mut i32) = value;
}
}
pub fn set_server_environment(&mut self, value: &str) {
unsafe {
*(self.0 .0.wrapping_add(88) as *mut usize) = ScString::from(value).0 as usize;
}
}
}
pub struct KeepAliveServerMessage(pub PiranhaMessage);
impl KeepAliveServerMessage {
pub fn new() -> Self {
import!(keep_alive_server_message_ctor(ptr: *const u8) -> () = 0x161838 + 1);
let instance = malloc(48);
keep_alive_server_message_ctor(instance);
Self(PiranhaMessage(instance))
}
}

View file

@ -0,0 +1,9 @@
use crate::{network::PiranhaMessage, sc_string::ScString};
pub struct ChangeAvatarNameMessage(pub PiranhaMessage);
impl ChangeAvatarNameMessage {
pub fn get_avatar_name(&self) -> String {
unsafe { ScString(*(self.0 .0.wrapping_add(48) as *const *const u8)).to_string() }
}
}

View file

@ -0,0 +1,132 @@
use crate::{
array_list::LogicArrayList,
import,
logic::{
avatar::{LogicClientAvatar, LogicNpcAvatar},
command::LogicCommand,
data::LogicNpcData,
home::LogicClientHome,
},
malloc,
network::PiranhaMessage,
sc_string::ScString,
};
pub struct OwnHomeDataMessage(pub PiranhaMessage);
impl OwnHomeDataMessage {
pub fn new() -> Self {
import!(own_home_data_message_ctor(ptr: *const u8) -> () = 0x16CD30 + 1);
let instance = malloc(104);
own_home_data_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_logic_client_home(&mut self, logic_client_home: LogicClientHome) {
unsafe {
*(self.0 .0.wrapping_add(68) as *mut usize) = logic_client_home.0 as usize;
}
}
pub fn set_logic_client_avatar(&mut self, logic_client_avatar: LogicClientAvatar) {
unsafe {
*(self.0 .0.wrapping_add(72) as *mut usize) = logic_client_avatar.0 as usize;
}
}
}
pub struct EndClientTurnMessage(pub PiranhaMessage);
impl EndClientTurnMessage {
pub fn get_sub_tick(&self) -> i32 {
unsafe { *(self.0 .0.wrapping_add(52) as *const i32) }
}
pub fn get_checksum(&self) -> i32 {
unsafe { *(self.0 .0.wrapping_add(56) as *const i32) }
}
pub fn get_commands(&self) -> Option<&LogicArrayList<LogicCommand>> {
unsafe {
let list_ptr =
*(self.0 .0.wrapping_add(48) as *const *const LogicArrayList<LogicCommand>);
(!list_ptr.is_null()).then_some(&*list_ptr)
}
}
}
pub struct OutOfSyncMessage(pub PiranhaMessage);
impl OutOfSyncMessage {
pub fn new() -> Self {
import!(out_of_sync_message_ctor(ptr: *const u8) -> () = 0x16CB60 + 1);
let instance = malloc(64);
out_of_sync_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_server_checksum(&mut self, value: i32) {
unsafe { *(self.0 .0.wrapping_add(48) as *mut i32) = value }
}
pub fn set_client_checksum(&mut self, value: i32) {
unsafe { *(self.0 .0.wrapping_add(52) as *mut i32) = value }
}
pub fn set_sub_tick(&mut self, value: i32) {
unsafe { *(self.0 .0.wrapping_add(56) as *mut i32) = value }
}
}
pub struct AvailableServerCommandMessage(pub PiranhaMessage);
impl AvailableServerCommandMessage {
pub fn new() -> Self {
import!(available_server_command_message_ctor(ptr: *const u8) -> () = 0x16B870 + 1);
let instance = malloc(100);
available_server_command_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_server_command(&mut self, command: &LogicCommand) {
unsafe { *(self.0 .0.wrapping_add(48) as *mut usize) = command.0 as usize }
}
}
pub struct AttackNpcMessage(pub PiranhaMessage);
impl AttackNpcMessage {
pub fn get_npc_data(&self) -> LogicNpcData {
unsafe { LogicNpcData(*(self.0 .0.wrapping_add(48) as *const *const u8)) }
}
}
pub struct NpcDataMessage(pub PiranhaMessage);
impl NpcDataMessage {
pub fn new() -> Self {
import!(npc_data_message_ctor(ptr: *const u8) -> () = 0x16C9E8 + 1);
let instance = malloc(64);
npc_data_message_ctor(instance);
Self(PiranhaMessage(instance))
}
pub fn set_level_json(&mut self, json: &str) {
unsafe {
*(self.0 .0.wrapping_add(52) as *mut usize) = ScString::from(json).0 as usize;
}
}
pub fn set_logic_client_avatar(&mut self, value: &LogicClientAvatar) {
unsafe {
*(self.0 .0.wrapping_add(56) as *mut usize) = value.0 as usize;
}
}
pub fn set_logic_npc_avatar(&mut self, value: &LogicNpcAvatar) {
unsafe {
*(self.0 .0.wrapping_add(60) as *mut usize) = value.0 as usize;
}
}
}

View file

@ -0,0 +1,7 @@
mod account;
mod avatar;
mod home;
pub use account::*;
pub use avatar::*;
pub use home::*;

139
libserver/src/network.rs Normal file
View file

@ -0,0 +1,139 @@
use tracing::info;
use crate::{import, malloc, sc_string::ScString};
pub struct Messaging(*const u8);
#[repr(C)]
pub struct Connection {
pub fd: i32,
pub is_connected: bool,
unk_1: i32,
unk_2: i32,
unk_3: i32,
}
impl Messaging {
pub fn new(fd: i32) -> Self {
import!(messaging_ctor(ptr: *const u8, queue_size: i32) -> () = 0x17476C + 1);
let instance = malloc(300);
messaging_ctor(instance, 50);
let mut messaging = Self(instance);
messaging.get_connection().fd = fd;
messaging.get_connection().is_connected = true;
messaging
}
pub fn set_encrypters(&mut self, encrypter: RC4Encrypter, decrypter: RC4Encrypter) {
import!(messaging_set_encrypters(ptr: *const u8, en: *const u8, de: *const u8, a4: i32) -> () = 0x17469C + 1);
messaging_set_encrypters(self.0, encrypter.0, decrypter.0, 0);
}
pub fn set_message_factory(&mut self, factory: *const LogicMagicMessageFactory) {
unsafe { *(self.0.wrapping_add(4) as *mut usize) = factory as usize }
}
pub fn on_receive(&mut self) {
import!(messaging_on_receive(ptr: *const u8, connection: *mut Connection) -> () = 0x175118 + 1);
unsafe { messaging_on_receive(self.0, std::mem::transmute(self.0.wrapping_add(64))) }
}
pub fn next_message(&mut self) -> Option<PiranhaMessage> {
import!(messaging_next_message(ptr: *const u8) -> usize = 0x174A92 + 1);
let message = messaging_next_message(self.0);
(message != 0).then_some(PiranhaMessage(message as *const u8))
}
pub fn send(&mut self, message: PiranhaMessage) {
import!(messaging_send(ptr: *const u8, message: *const u8) -> () = 0x174BD8 + 1);
info!(
"Messaging::send: sending message of type {}",
message.get_message_type()
);
messaging_send(self.0, message.0);
}
pub fn on_wakeup(&mut self) {
import!(messaging_on_wakeup(ptr: *const u8, connection: *mut Connection) -> () = 0x1749A4 + 1);
unsafe { messaging_on_wakeup(self.0, std::mem::transmute(self.0.wrapping_add(64))) }
}
pub fn get_connection(&mut self) -> &mut Connection {
unsafe { std::mem::transmute(self.0.wrapping_add(64)) }
}
pub fn scramble_nonce_using_mersenne_twister(seed: i32, nonce: &mut [u8]) {
import!(messaging_scramble_nonce_using_mersenne_twister(seed: i32, nonce: *const u8, nonce_len: i32) -> () = 0x1A62E4 + 1);
messaging_scramble_nonce_using_mersenne_twister(seed, nonce.as_ptr(), nonce.len() as i32);
}
}
#[repr(transparent)]
pub struct PiranhaMessage(pub *const u8);
impl PiranhaMessage {
pub fn get_message_type(&self) -> u16 {
unsafe {
let fn_ptr = ((*(self.0 as *const usize)) + 20) as *const usize;
std::mem::transmute::<_, extern "C" fn(*const u8) -> u16>(*fn_ptr)(self.0)
}
}
}
#[repr(C)]
pub struct LogicMagicMessageFactory {
vtable: usize,
}
impl LogicMagicMessageFactory {
pub const RC4_KEY: &str = "fhsd6f86f67rt8fw78fw789we78r9789wer6re";
pub fn new() -> Self {
import!(logic_magic_message_factory_ctor(ptr: *mut u8) -> () = 0x1497DC + 1);
let mut instance = Self { vtable: 0 };
unsafe {
logic_magic_message_factory_ctor(std::mem::transmute(&mut instance));
}
instance
}
}
#[repr(transparent)]
pub struct RC4Encrypter(*const u8);
impl RC4Encrypter {
pub fn new(key: &str, nonce: &str) -> Self {
import!(rc4_encrypter_ctor(ptr: *const u8, key: *const u8, nonce: *const u8) -> () = 0x188D0C + 1);
let instance = malloc(268);
rc4_encrypter_ctor(instance, ScString::from(key).0, ScString::from(nonce).0);
Self(instance)
}
pub fn new_with_nonce_bytes(key: &str, nonce: &[u8]) -> Self {
import!(rc4_encrypter_ctor(ptr: *const u8, key: *const u8, nonce: *const u8, nonce_len: i32) -> () = 0x188CD8 + 1);
let instance = malloc(268);
let nonce_bytes = malloc(nonce.len());
unsafe {
std::slice::from_raw_parts_mut(nonce_bytes as *mut u8, nonce.len())
.copy_from_slice(nonce);
};
rc4_encrypter_ctor(
instance,
ScString::from(key).0,
nonce_bytes,
nonce.len() as i32,
);
Self(instance)
}
}

139
libserver/src/resources.rs Normal file
View file

@ -0,0 +1,139 @@
use std::ffi::CString;
use crate::{import, logic::json::LogicJSONNode, malloc, sc_string::ScString};
pub struct ResourceManager;
impl ResourceManager {
pub fn get_json(path: &str) -> LogicJSONNode {
import!(resource_manager_get_json(path: *const u8) -> *const u8 = 0x18A750 + 1);
LogicJSONNode(resource_manager_get_json(
CString::new(path).unwrap().as_ptr(),
))
}
}
#[repr(transparent)]
struct DataLoaderFactory(*const u8);
impl DataLoaderFactory {
pub fn new() -> Self {
import!(data_loader_factory_ctor(ptr: *const u8) -> () = 0x18967C + 1);
let instance = malloc(4);
data_loader_factory_ctor(instance);
Self(instance)
}
}
#[repr(transparent)]
struct ResourceListener(*const u8);
impl ResourceListener {
pub fn new() -> Self {
import!(resource_listener_ctor(ptr: *const u8) -> () = 0x189464 + 1);
let instance = malloc(20);
resource_listener_ctor(instance);
Self(instance)
}
pub fn add_file(&self, name: &str) {
import!(resource_listener_add_file(ptr: *const u8, name: *const u8, a3: i32, a4: i32, a5: i32, a6: i32) -> () = 0x18B1C0 + 1);
resource_listener_add_file(self.0, ScString::from(name).0, -1, -1, -1, -1);
}
pub fn start_loading(&self) {
import!(resource_listener_start_loading(ptr: *const u8) -> () = 0x18B50C + 1);
resource_listener_start_loading(self.0);
}
}
pub fn init() {
const CSV_FILES: &[&str] = &[
"logic/buildings.csv",
"logic/locales.csv",
"logic/resources.csv",
"logic/characters.csv",
"csv/animations.csv",
"logic/projectiles.csv",
"csv/texts.csv",
"csv/texts_patch.csv",
"logic/regions.csv",
"logic/building_classes.csv",
"logic/obstacles.csv",
"logic/effects.csv",
"csv/particle_emitters.csv",
"logic/experience_levels.csv",
"logic/traps.csv",
"logic/alliance_badges.csv",
"logic/alliance_badge_layers.csv",
"logic/globals.csv",
"csv/client_globals.csv",
"logic/townhall_levels.csv",
"logic/alliance_portal.csv",
"logic/npcs.csv",
"logic/decos.csv",
"csv/resource_packs.csv",
"logic/shields.csv",
"logic/missions.csv",
"csv/billing_packages.csv",
"logic/achievements.csv",
"csv/credits.csv",
"csv/faq.csv",
"logic/spells.csv",
"csv/hints.csv",
"logic/heroes.csv",
"logic/leagues.csv",
"csv/news.csv",
"logic/war.csv",
"logic/alliance_levels.csv",
"csv/helpshift.csv",
];
const NPCS_COUNT: usize = 48;
const PREBASES_COUNT: usize = 11;
import!(resource_manager_init(factory: DataLoaderFactory, a2: *const u8) -> () = 0x18B898 + 1);
import!(resource_manager_resource_to_load() -> i32 = 0x1894B8 + 1);
import!(resource_manager_load_next_resource() -> () = 0x18A9EC + 1);
import!(logic_data_tables_init() -> () = 0x12CF54 + 1);
import!(logic_resources_create_data_table_resources_array() -> *const u8 = 0x136978 + 1);
import!(resource_manager_get_csv(csv: *const u8) -> *const u8 = 0x18A816 + 1);
import!(logic_resources_load(data_table_resources_array: *const u8, index: i32, csv: *const u8) -> *const u8 = 0x1368A8 + 1);
let data_loader_factory = DataLoaderFactory::new();
resource_manager_init(data_loader_factory, [0x00].as_ptr());
logic_data_tables_init();
let listener = ResourceListener::new();
listener.add_file("level/starting_home.json");
for i in 1..=NPCS_COUNT {
listener.add_file(&format!("level/npc{i}.json"));
}
for i in 1..=PREBASES_COUNT {
listener.add_file(&format!("level/townhall{i}.json"));
}
listener.add_file("level/tutorial_npc.json");
listener.add_file("level/tutorial_npc2.json");
for path in CSV_FILES {
listener.add_file(path);
}
listener.start_loading();
while resource_manager_resource_to_load() != 0 {
resource_manager_load_next_resource();
}
let data_table_resources_array = logic_resources_create_data_table_resources_array();
for (index, path) in CSV_FILES.iter().enumerate() {
let csv = resource_manager_get_csv(ScString::from(path).0);
logic_resources_load(data_table_resources_array, index as i32, csv);
}
}

View file

@ -0,0 +1,79 @@
use std::{
ffi::{CStr, CString},
fmt,
};
use crate::{import, malloc};
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct ScString(pub *const u8);
impl<S> From<S> for ScString
where
S: AsRef<str>,
{
fn from(value: S) -> Self {
import!(string_ctor(ptr: *const u8, data: *const u8) -> () = 0x176748 + 1);
let sc_string = malloc(32);
string_ctor(sc_string, CString::new(value.as_ref()).unwrap().as_ptr());
Self(sc_string)
}
}
impl fmt::Display for ScString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let length = unsafe { *(self.0.wrapping_add(4) as *const i32) as usize };
if length + 1 > 8 {
let c_string = unsafe { *(self.0.wrapping_add(8) as *const *const u8) };
unsafe {
write!(
f,
"{}",
CStr::from_ptr(c_string).to_string_lossy().to_string()
)
}
} else if length > 0 {
unsafe {
write!(
f,
"{}",
CStr::from_ptr(self.0.wrapping_add(8) as *const u8)
.to_string_lossy()
.to_string()
)
}
} else {
Ok(())
}
}
}
impl fmt::Debug for ScString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
#[repr(transparent)]
pub struct StringBuilder(pub *const u8);
impl StringBuilder {
pub fn new() -> Self {
import!(string_builder_ctor(ptr: *const u8) -> () = 0x1772BA + 1);
let instance = malloc(12);
string_builder_ctor(instance);
Self(instance)
}
}
impl fmt::Display for StringBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
import!(string_builder_to_string(sc_str: *const u8, ptr: *const u8) -> () = 0x1772D8 + 1);
let sc_str = ScString::from("");
string_builder_to_string(sc_str.0, self.0);
write!(f, "{sc_str}")
}
}

BIN
screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB