diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0e4c7ae..f444d2a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,17 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -283,12 +272,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.21.7" @@ -301,12 +284,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bit-set" version = "0.8.0" @@ -562,16 +539,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -597,12 +564,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "convert_case" version = "0.4.0" @@ -727,18 +688,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.7" @@ -839,17 +788,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.5.8" @@ -912,9 +850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -1050,46 +986,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "embed-resource" version = "3.0.8" @@ -1114,10 +1016,8 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" name = "emerald-legacy-launcher" version = "1.2.1" dependencies = [ - "aes", "base64 0.21.7", "byteorder", - "cipher", "flate2", "futures-util", "http 1.4.0", @@ -1125,14 +1025,11 @@ dependencies = [ "libc", "lzxd", "once_cell", - "p256", "rand 0.8.5", "reqwest 0.11.27", "rfd", - "rsa", "serde", "serde_json", - "sha1", "steam_shortcuts_util", "tauri", "tauri-build", @@ -1282,16 +1179,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "field-offset" version = "0.3.6" @@ -1607,7 +1494,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1799,17 +1685,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "gtk" version = "0.18.2" @@ -1937,15 +1812,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "html5ever" version = "0.29.1" @@ -2362,15 +2228,6 @@ dependencies = [ "libc", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -2545,15 +2402,6 @@ dependencies = [ "selectors 0.24.0", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - [[package]] name = "leb128fmt" version = "0.1.0" @@ -2616,12 +2464,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - [[package]] name = "libredox" version = "0.1.15" @@ -2899,48 +2741,12 @@ dependencies = [ "nom", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2948,7 +2754,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3212,18 +3017,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - [[package]] name = "pango" version = "0.18.3" @@ -3284,15 +3077,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -3503,27 +3287,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -3629,15 +3392,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -4046,16 +3800,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "rfd" version = "0.15.4" @@ -4107,26 +3851,6 @@ dependencies = [ "uuid 0.8.2", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -4330,20 +4054,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -4631,16 +4341,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.9" @@ -4739,22 +4439,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 04e375d..29c600c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -34,15 +34,10 @@ image = "0.24" steam_shortcuts_util = "1.1.8" tauri-plugin-process = "2" byteorder = "1" -p256 = "0.13" flate2 = "1" -sha1 = "0.10" once_cell = "1" lzxd = "0.2.6" uuid = { version = "1", features = ["v4"] } -rsa = { version = "0.9", default-features = false, features = ["std"] } -aes = "0.8" -cipher = "0.4" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-updater = "2" diff --git a/src-tauri/src/commands/bridge.rs b/src-tauri/src/commands/bridge.rs deleted file mode 100644 index cbba0c8..0000000 --- a/src-tauri/src/commands/bridge.rs +++ /dev/null @@ -1,63 +0,0 @@ -use tauri::{AppHandle, Emitter}; -use crate::lce_bridge::{start_bridge, stop_bridge, bridge_status as lce_bridge_status, config::BridgeConfig, msa_auth}; -use std::sync::OnceLock; - -static AUTH_PROFILE: OnceLock = OnceLock::new(); - -#[allow(dead_code)] -pub fn get_auth_profile() -> Option<&'static msa_auth::MinecraftProfile> { - AUTH_PROFILE.get() -} - -#[tauri::command] -#[allow(non_snake_case)] -pub async fn bridge_start( - app: AppHandle, - listen_address: String, - port: u16, - remote_host: String, - remote_port: u16, - auth_type: String, - remote_protocol_version: i32, -) -> Result { - if auth_type == "online" { - let mut rx = msa_auth::subscribe_device_code(); - let app2 = app.clone(); - tokio::spawn(async move { - while let Ok(msg) = rx.recv().await { - let _ = app2.emit("lcebridge-msa-code", &msg); - } - }); - let profile = msa_auth::authenticate().await - .map_err(|e| format!("MSA auth failed: {e}"))?; - let _ = AUTH_PROFILE.set(profile); - } - - let config = BridgeConfig { - lce: crate::lce_bridge::config::LceConfig { - listen_address, - port, - motd: "LCEBridge".to_string(), - }, - remote: crate::lce_bridge::config::RemoteConfig { - host: remote_host, - port: remote_port, - auth_type, - protocol_version: remote_protocol_version, - }, - ..Default::default() - }; - start_bridge(config).await -} - -#[tauri::command] -#[allow(non_snake_case)] -pub async fn bridge_stop() -> Result { - stop_bridge().await -} - -#[tauri::command] -#[allow(non_snake_case)] -pub async fn bridge_status() -> Result { - lce_bridge_status().await -} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index d6802b0..41461d5 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,4 +1,3 @@ -pub mod bridge; pub mod config; pub mod console2lce; pub mod download; diff --git a/src-tauri/src/lce_bridge/bridge.rs b/src-tauri/src/lce_bridge/bridge.rs deleted file mode 100644 index fc4ab5a..0000000 --- a/src-tauri/src/lce_bridge/bridge.rs +++ /dev/null @@ -1,622 +0,0 @@ -use crate::lce_bridge::lce_packets::*; -use crate::lce_bridge::java_protocol::*; -use crate::lce_bridge::java_session::JavaSession; -use crate::lce_bridge::chunk::{translate_java_chunk, CachedLceChunk}; -use crate::lce_bridge::config::BridgeConfig; -use crate::lce_bridge::crafting::InventoryManager; -use std::collections::{HashMap, VecDeque}; -use uuid::Uuid; - -pub struct LceBridgeSession { - pub config: BridgeConfig, - pub lce_tx: VecDeque, - pub java_session: Option, - pub player_eid: i32, - pub tracked_entities: HashMap, - pub chunk_cache: HashMap<(i32, i32), CachedLceChunk>, - pub inventory: InventoryManager, - pub render_distance: i32, - pub teleport_id: i32, - pub health: f32, - pub food: i32, - pub saturation: f32, - pub game_time: i64, - pub day_time: i64, - pub has_joined: bool, - pub player_name: String, - pub pos_x: f64, - pub pos_y: f64, - pub pos_z: f64, - pub yaw: f32, - pub pitch: f32, - pub container_id_map: HashMap, - pub lce_container_id: u8, -} - -pub struct TrackedEntity { - pub entity_id: i32, - pub kind: TrackedEntityKind, - pub x: f64, - pub y: f64, - pub z: f64, - pub yaw: u8, - pub pitch: u8, -} - -pub enum TrackedEntityKind { - Object, - Mob(u8), - Item, - Player, -} - -impl LceBridgeSession { - pub fn new(config: BridgeConfig) -> Self { - Self { - config, - lce_tx: VecDeque::new(), - java_session: None, - player_eid: 0, - tracked_entities: HashMap::new(), - chunk_cache: HashMap::new(), - inventory: InventoryManager::new(), - render_distance: 8, - teleport_id: 0, - health: 20.0, - food: 20, - saturation: 5.0, - game_time: 0, - day_time: 1000, - has_joined: false, - player_name: String::new(), - pos_x: 0.0, pos_y: 64.0, pos_z: 0.0, - yaw: 0.0, pitch: 0.0, - container_id_map: HashMap::new(), - lce_container_id: 1, - } - } - - pub fn send_lce(&mut self, packet: LcePacket) { - self.lce_tx.push_back(packet); - } - - pub fn on_connected(&mut self) { - eprintln!("[LCEBridge] on_connected"); - let prelogin = LcePacket::PreLogin(PreLoginPacket { - net_version: 560, - player_name: "Player".to_string(), - offline_xuid: 0, - online_xuid: 0, - }); - self.send_lce(prelogin); - } - - pub async fn handle_lce_packet(&mut self, packet: LcePacket) { - match packet { - LcePacket::PreLogin(p) => { - self.on_lce_prelogin(p).await; - } - LcePacket::Login(p) => { - self.on_lce_login(p).await; - } - LcePacket::MovePlayer(p) => { - self.pos_x = p.x; - self.pos_y = p.y; - self.pos_z = p.z; - self.yaw = p.yaw; - self.pitch = p.pitch; - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::AcceptTeleportation(AcceptTeleportationPacket { - teleport_id: self.teleport_id, - })).await; - } - } - LcePacket::Chat(p) => { - if let Some(ref mut js) = self.java_session { - let msg = p.string_args.first().cloned().unwrap_or_default(); - let _ = js.send(JavaPacket::ChatCommand(ChatCommandPacket { command: msg })); - } - } - LcePacket::PlayerAction(p) => { - if let Some(ref mut js) = self.java_session { - if p.action == 1 { - let _x = p.x as f64; - let _y = p.y as f64; - let _z = p.z as f64; - let _face = p.face; - let _ = js.send(JavaPacket::Unknown { id: 0x36, data: VarInt::write(0) }); - } - } - } - LcePacket::Interact(p) => { - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::Unknown { - id: 0x11, - data: pack_varint(p.target), - }).await; - } - } - LcePacket::ContainerClick(p) => { - if let Some(ref mut js) = self.java_session { - let mut data = Vec::new(); - VarInt::write_to(p.container_id as i32, &mut data); - VarInt::write_to(0, &mut data); - VarInt::write_to(p.slot_num as i32, &mut data); - data.push(p.click_type); - data.push(p.button_num); - VarInt::write_to(0, &mut data); - let _ = js.send(JavaPacket::Unknown { id: 0x0E, data }).await; - } - } - LcePacket::ContainerClose(p) => { - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::ContainerCloseS2C( - ContainerCloseS2CPacket { container_id: p.container_id as i32 } - )).await; - } - } - LcePacket::SetCarriedItem(p) => { - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::Unknown { - id: 0x28, - data: vec![p.slot], - }).await; - } - } - LcePacket::ClientCommand(_p) => { - //neo: do NOT think of sending here. it causes premature FinishConfiguration - } - LcePacket::RespawnRequest(_) => {} - _ => {} - } - } - - async fn on_lce_prelogin(&mut self, p: PreLoginPacket) { - eprintln!("[LCEBridge] PreLogin from client: player={}, net_ver={}, offline_xuid={}, online_xuid={}", p.player_name, p.net_version, p.offline_xuid, p.online_xuid); - self.player_name = p.player_name.clone(); - eprintln!("[LCEBridge] Connecting to Java server at {}:{}", self.config.remote.host, self.config.remote.port); - match JavaSession::connect(&self.config.remote).await { - Ok(mut js) => { - eprintln!("[LCEBridge] Connected to Java server, sending Handshake+LoginStart"); - let handshake = JavaPacket::Handshake(HandshakePacket { - protocol_version: self.config.remote.protocol_version, - server_address: self.config.remote.host.clone(), - server_port: self.config.remote.port, - next_state: 2, - }); - let _ = js.send(handshake).await; - let login_start = JavaPacket::LoginStart(LoginStartPacket { - username: p.player_name.clone(), - profile_id: Uuid::nil(), - protocol_version: self.config.remote.protocol_version, - }); - let _ = js.send(login_start).await; - eprintln!("[LCEBridge] Handshake+LoginStart sent to Java server"); - self.java_session = Some(js); - self.has_joined = true; - } - Err(e) => { - eprintln!("[LCEBridge] Failed to connect to Java server: {}", e); - self.send_lce(LcePacket::Disconnect(DisconnectPacket { reason: 1 })); - } - } - } - - async fn on_lce_login(&mut self, p: LoginPacket) { - if self.java_session.is_none() { - eprintln!("[LCEBridge] Client sent Login without PreLogin, connecting to Java server now"); - match JavaSession::connect(&self.config.remote).await { - Ok(mut js) => { - let handshake = JavaPacket::Handshake(HandshakePacket { - protocol_version: self.config.remote.protocol_version, - server_address: self.config.remote.host.clone(), - server_port: self.config.remote.port, - next_state: 2, - }); - let _ = js.send(handshake).await; - let login_start = JavaPacket::LoginStart(LoginStartPacket { - username: p.username.clone(), - profile_id: Uuid::nil(), - protocol_version: self.config.remote.protocol_version, - }); - let _ = js.send(login_start).await; - self.java_session = Some(js); - self.has_joined = true; - } - Err(e) => { - eprintln!("[LCEBridge] Failed to connect to Java server: {}", e); - } - } - } - self.send_lce_login_response(&p.username).await; - self.player_name.clear(); - } - - async fn send_lce_login_response(&mut self, username: &str) { - self.send_lce(LcePacket::Login(LoginPacket { - protocol_version: 560, - username: username.to_string(), - map_seed: 0, - game_type: 0, - world_name: "bridge".to_string(), - dimension: 0, - difficulty: 2, - max_players: 1, - world_width: 0, - world_length: 0, - })); - self.send_lce(LcePacket::SetSpawnPosition(SetSpawnPositionPacket { - x: 0, y: 64, z: 0, - })); - self.send_lce(LcePacket::GameEvent(GameEventPacket { - reason: 3, param: 0.0, - })); - self.send_lce(LcePacket::SetTime(SetTimePacket { - game_time: 0, day_time: 1000, - })); - self.send_lce(LcePacket::PlayerAbilities(PlayerAbilitiesPacket { - flags: 0x04, fly_speed: 0.05, walk_speed: 0.1, - })); - self.send_lce(LcePacket::SetHealth(SetHealthPacket { - health: 20.0, food: 20, saturation: 5.0, damage_source: 0, - })); - let _ = self.spawn_player().await; - } - - async fn spawn_player(&mut self) { - let (cx, cz) = (0i32, 0i32); - self.send_lce(LcePacket::ChunkVisibilityArea(ChunkVisibilityAreaPacket { - min_cx: cx - self.render_distance, - max_cx: cx + self.render_distance, - min_cz: cz - self.render_distance, - max_cz: cz + self.render_distance, - })); - for dx in -self.render_distance..=self.render_distance { - for dz in -self.render_distance..=self.render_distance { - self.send_lce(LcePacket::ChunkVisibility(ChunkVisibilityPacket { - chunk_x: cx + dx, - chunk_z: cz + dz, - visible: true, - })); - } - } - let builder = crate::lce_bridge::chunk::LceChunkBuilder::new(); - let raw = builder.build_raw_data(); - let compressed = crate::lce_bridge::chunk::compress_rle_zlib(&raw); - self.send_lce(LcePacket::BlockRegionUpdate(BlockRegionUpdatePacket { - x: 0, y: -64, z: 0, xs: 16, ys: 24, zs: 16, - level_idx: 0, is_full_chunk: true, compressed_data: compressed, - })); - } - - pub async fn handle_java_packet(&mut self, packet: JavaPacket) { - match packet { - JavaPacket::KeepAliveS2C(p) => { - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::KeepAliveC2S(KeepAliveC2SPacket { id: p.id })).await; - } - } - JavaPacket::LoginPlay(p) => { - self.player_eid = p.entity_id; - self.render_distance = p.view_distance.min(self.render_distance); - if !self.player_name.is_empty() { - let name = self.player_name.clone(); - self.send_lce_login_response(&name).await; - self.player_name.clear(); - } - } - JavaPacket::SynchronizePlayerPosition(p) => { - self.teleport_id = p.teleport_id; - self.pos_x = p.x; - self.pos_y = p.y; - self.pos_z = p.z; - self.yaw = p.yaw; - self.pitch = p.pitch; - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::ClientInformation(ClientInformationPacket { - locale: "en_GB".to_string(), - view_distance: self.render_distance as u8, - chat_mode: 0, - chat_colors: true, - displayed_skin_parts: 0x7F, - main_hand: 1, - enable_text_filtering: false, - allow_listing: false, - particle_status: 0, - })).await; - let _ = js.send(JavaPacket::AcceptTeleportation(AcceptTeleportationPacket { - teleport_id: p.teleport_id, - })).await; - } - } - JavaPacket::PlayerPosition(p) => { - self.teleport_id = p.teleport_id; - if let Some(ref mut js) = self.java_session { - let _ = js.send(JavaPacket::ClientInformation(ClientInformationPacket { - locale: "en_GB".to_string(), - view_distance: self.render_distance as u8, - chat_mode: 0, - chat_colors: true, - displayed_skin_parts: 0x7F, - main_hand: 1, - enable_text_filtering: false, - allow_listing: false, - particle_status: 0, - })).await; - let _ = js.send(JavaPacket::AcceptTeleportation(AcceptTeleportationPacket { - teleport_id: p.teleport_id, - })).await; - } - self.send_lce(LcePacket::SetSpawnPosition(SetSpawnPositionPacket { - x: p.x as i32, y: p.y as i32, z: p.z as i32, - })); - } - JavaPacket::ChunkData(p) => { - let (_builder, compressed) = translate_java_chunk( - p.chunk_x, p.chunk_z, &p.chunk_data, - ); - self.chunk_cache.insert( - (p.chunk_x, p.chunk_z), - CachedLceChunk::new(p.chunk_x, p.chunk_z), - ); - self.send_lce(LcePacket::ChunkVisibility(ChunkVisibilityPacket { - chunk_x: p.chunk_x, - chunk_z: p.chunk_z, - visible: true, - })); - self.send_lce(LcePacket::BlockRegionUpdate(BlockRegionUpdatePacket { - x: p.chunk_x * 16, y: -64, z: p.chunk_z * 16, - xs: 16, ys: 24, zs: 16, - level_idx: 0, is_full_chunk: true, - compressed_data: compressed, - })); - } - JavaPacket::ChunkBatchFinished => {} - JavaPacket::SetContainerContent(p) => { - let mut items = Vec::new(); - for slot in &p.slots { - if slot.present { - items.push(LceItemStack { - id: slot.item_id as i16, - count: slot.count, - damage: 0, - }); - } else { - items.push(LceItemStack::empty()); - } - } - let cid = *self.container_id_map.get(&p.container_id).unwrap_or(&1); - self.send_lce(LcePacket::ContainerSetContent(ContainerSetContentPacket { - container_id: cid, - items, - })); - } - JavaPacket::SetContainerSlot(p) => { - let item = if p.item.present { - LceItemStack { - id: p.item.item_id as i16, - count: p.item.count, - damage: 0, - } - } else { - LceItemStack::empty() - }; - let cid = *self.container_id_map.get(&p.container_id).unwrap_or(&1); - self.send_lce(LcePacket::ContainerSetSlot(ContainerSetSlotPacket { - container_id: cid, - slot: p.slot, - item, - })); - } - JavaPacket::OpenScreen(p) => { - let lce_type = match p.screen_id { - 2 | 3 | 4 | 5 => 16, - 7 => 17, - 8 => 15, - 9 | 10 | 11 | 12 | 13 | 14 => 14, - _ => 14, - }; - let cid = self.lce_container_id; - self.lce_container_id += 1; - self.container_id_map.insert(p.container_id, cid); - self.send_lce(LcePacket::ContainerOpen(ContainerOpenPacket { - container_id: cid, - container_type: lce_type, - size: 0, - custom_name: String::new(), - title: String::new(), - entity_id: -1, - })); - } - JavaPacket::SystemChat(p) => { - let text = extract_chat_text(&p.content_json); - self.send_lce(LcePacket::Chat(ChatPacket::set_message(&text))); - } - JavaPacket::PlayerChat(p) => { - let text = extract_chat_text(&p.content_json); - self.send_lce(LcePacket::Chat(ChatPacket::set_message(&text))); - } - JavaPacket::SetHealth(p) => { - self.health = p.health; - self.food = p.food; - self.saturation = p.saturation; - self.send_lce(LcePacket::SetHealth(SetHealthPacket { - health: p.health, food: p.food, saturation: p.saturation, damage_source: 0, - })); - } - JavaPacket::SetTime(p) => { - self.game_time = p.world_age; - self.day_time = p.day_time; - self.send_lce(LcePacket::SetTime(SetTimePacket { - game_time: p.world_age, day_time: p.day_time, - })); - } - JavaPacket::AddEntity(p) => { - let lce_type = map_java_object_type(p.entity_type); - let fx = (p.x * 32.0) as i32; - let fy = (p.y * 32.0) as i32; - let fz = (p.z * 32.0) as i32; - self.tracked_entities.insert(p.entity_id, TrackedEntity { - entity_id: p.entity_id, - kind: TrackedEntityKind::Object, - x: p.x, y: p.y, z: p.z, - yaw: p.yaw, pitch: p.pitch, - }); - self.send_lce(LcePacket::AddEntity(AddEntityPacket { - entity_id: p.entity_id, - entity_type: lce_type, - x: fx, y: fy, z: fz, - yaw: p.yaw, pitch: p.pitch, - data: p.data, - motion_x: p.vel_x, motion_y: p.vel_y, motion_z: p.vel_z, - })); - } - JavaPacket::AddMob(p) => { - let lce_type = map_java_mob_type(p.entity_type); - let fx = (p.x * 32.0) as i32; - let fy = (p.y * 32.0) as i32; - let fz = (p.z * 32.0) as i32; - self.tracked_entities.insert(p.entity_id, TrackedEntity { - entity_id: p.entity_id, - kind: TrackedEntityKind::Mob(lce_type), - x: p.x, y: p.y, z: p.z, - yaw: p.yaw, pitch: p.pitch, - }); - self.send_lce(LcePacket::AddMob(AddMobPacket { - entity_id: p.entity_id, - entity_type: lce_type, - x: fx, y: fy, z: fz, - yaw: p.yaw, pitch: p.pitch, head_yaw: p.head_yaw, - motion_x: p.vel_x, motion_y: p.vel_y, motion_z: p.vel_z, - metadata: Vec::new(), - })); - } - JavaPacket::AddPlayer(p) => { - let fx = (p.x * 32.0) as i32; - let fy = (p.y * 32.0) as i32; - let fz = (p.z * 32.0) as i32; - self.send_lce(LcePacket::AddPlayer(AddPlayerPacket { - entity_id: p.entity_id, - name: String::new(), - x: fx, y: fy, z: fz, - yaw: p.yaw, pitch: p.pitch, head_yaw: p.yaw, - carried_item: 0, - offline_xuid: p.entity_id as i64, - online_xuid: 0, - player_index: 0, - skin_id: String::new(), - cape_id: String::new(), - game_privileges: 0xFFFFFFFF, - metadata: Vec::new(), - })); - } - JavaPacket::RemoveEntities(p) => { - for id in &p.entity_ids { - self.tracked_entities.remove(id); - } - self.send_lce(LcePacket::RemoveEntities(RemoveEntitiesPacket { - entity_ids: p.entity_ids, - })); - } - JavaPacket::SetEntityMotion(p) => { - self.send_lce(LcePacket::SetEntityMotion(SetEntityMotionPacket { - entity_id: p.entity_id, - xa: p.xa, ya: p.ya, za: p.za, - })); - } - JavaPacket::TeleportEntity(p) => { - let fx = (p.x * 32.0) as i32; - let fy = (p.y * 32.0) as i32; - let fz = (p.z * 32.0) as i32; - if let Some(e) = self.tracked_entities.get_mut(&p.entity_id) { - e.x = p.x; e.y = p.y; e.z = p.z; - } - self.send_lce(LcePacket::TeleportEntity(TeleportEntityPacket { - entity_id: p.entity_id, - x: fx, y: fy, z: fz, - yaw: p.yaw, pitch: p.pitch, - })); - } - JavaPacket::SetHeadRotation(p) => { - self.send_lce(LcePacket::RotateHead(RotateHeadPacket { - entity_id: p.entity_id, - y_head_rot: p.head_yaw, - })); - } - JavaPacket::SetEntityData(_) => {} - JavaPacket::GameEventS2C(p) => { - self.send_lce(LcePacket::GameEvent(GameEventPacket { - reason: p.reason, param: p.param, - })); - } - JavaPacket::SetDefaultSpawnPosition(p) => { - self.send_lce(LcePacket::SetSpawnPosition(SetSpawnPositionPacket { - x: p.x, y: p.y, z: p.z, - })); - } - JavaPacket::ContainerCloseS2C(p) => { - self.send_lce(LcePacket::ContainerClose(ContainerClosePacket { - container_id: p.container_id as u8, - })); - } - JavaPacket::ContainerSetDataS2C(p) => { - self.send_lce(LcePacket::ContainerSetData(ContainerSetDataPacket { - container_id: p.container_id as u8, - id: p.key as i16, - value: p.value as i16, - })); - } - JavaPacket::ContainerAckS2C(p) => { - self.send_lce(LcePacket::ContainerAck(ContainerAckPacket { - container_id: p.container_id, - uid: p.uid, - accepted: p.accepted, - })); - } - JavaPacket::SoundEffect(_) => {} - JavaPacket::RegistryData(_) => {} - _ => {} - } - } -} - -fn map_java_mob_type(java_type: i32) -> u8 { - match java_type { - 0 => 60, 1 => 61, 2 => 62, 3 => 63, 4 => 64, 5 => 65, 6 => 66, - 7 => 67, 8 => 68, 10 => 69, 11 => 70, 12 => 71, 13 => 72, - 14 => 73, 15 => 74, 16 => 75, 17 => 76, 18 => 77, 19 => 78, - 20 => 79, 21 => 80, 22 => 81, 23 => 82, 24 => 83, 25 => 84, - 26 => 85, 27 => 86, 28 => 87, 29 => 88, 30 => 89, 31 => 90, - 32 => 91, 33 => 92, 34 => 93, 35 => 94, 36 => 95, 37 => 96, - 38 => 97, 39 => 98, 40 => 99, - _ => 60, - } -} - -fn map_java_object_type(java_type: i32) -> u8 { - match java_type { - 0 => 50, 1 => 51, 2 => 52, 3 => 53, 4 => 54, 5 => 55, - 6 => 56, 7 => 57, 8 => 58, 9 => 59, 10 => 60, 11 => 61, - _ => 50, - } -} - -fn extract_chat_text(nbt_data: &[u8]) -> String { - if nbt_data.is_empty() || nbt_data[0] == 0 { - return String::new(); - } - let text = String::from_utf8_lossy(nbt_data); - if let Some(start) = text.find("text\":\"") { - let rest = &text[start + 7..]; - if let Some(end) = rest.find('"') { - return rest[..end].to_string(); - } - } - String::new() -} - -fn pack_varint(val: i32) -> Vec { - VarInt::write(val) -} - -use crate::lce_bridge::util::VarInt; diff --git a/src-tauri/src/lce_bridge/chunk.rs b/src-tauri/src/lce_bridge/chunk.rs deleted file mode 100644 index 9aeb3ae..0000000 --- a/src-tauri/src/lce_bridge/chunk.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::lce_bridge::registry; -use flate2::write::ZlibEncoder; -use flate2::Compression; -use std::io::Write; - -const SECTIONS: usize = 24; -const BLOCKS_PER_SECTION: usize = 4096; -pub const FULL_CHUNK_SIZE: usize = SECTIONS * BLOCKS_PER_SECTION; - -pub struct LceChunkBuilder { - blocks: Vec, - block_light: Vec, - sky_light: Vec, - biomes: Vec, -} - -impl LceChunkBuilder { - pub fn new() -> Self { - Self { - blocks: vec![0u16; FULL_CHUNK_SIZE], - block_light: vec![0u8; FULL_CHUNK_SIZE / 2], - sky_light: vec![0u8; FULL_CHUNK_SIZE / 2], - biomes: vec![0u8; 256], - } - } - - pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: u16) { - if y >= SECTIONS * 16 { return; } - let seci = y / 16; - let lcy = y % 16; - let idx = seci * BLOCKS_PER_SECTION + (x * 16 + z) * 16 + lcy; - if idx < self.blocks.len() { - self.blocks[idx] = block; - } - } - - pub fn set_sky_light(&mut self, x: usize, y: usize, z: usize, light: u8) { - if y >= SECTIONS * 16 { return; } - let idx = (y / 16) * 2048 + (x * 16 + z) * 16 + (y % 16); - if idx < self.sky_light.len() { - let nibble_idx = idx / 2; - if idx % 2 == 0 { - self.sky_light[nibble_idx] = (self.sky_light[nibble_idx] & 0xF0) | (light & 0x0F); - } else { - self.sky_light[nibble_idx] = (self.sky_light[nibble_idx] & 0x0F) | ((light & 0x0F) << 4); - } - } - } - - pub fn set_biome(&mut self, x: usize, z: usize, biome: u8) { - let idx = z * 16 + x; - if idx < self.biomes.len() { - self.biomes[idx] = biome; - } - } - - pub fn build_raw_data(&self) -> Vec { - let mut data = Vec::with_capacity( - self.blocks.len() * 2 + self.sky_light.len() + self.block_light.len() + self.biomes.len() - ); - for &b in &self.blocks { - data.extend_from_slice(&b.to_be_bytes()); - } - data.extend_from_slice(&self.sky_light); - data.extend_from_slice(&self.block_light); - data.extend_from_slice(&self.biomes); - data - } - - pub fn snapshot(&self) -> Self { - Self { - blocks: self.blocks.clone(), - block_light: self.block_light.clone(), - sky_light: self.sky_light.clone(), - biomes: self.biomes.clone(), - } - } - - pub fn pack_half_height_nibbles(&self) -> (Vec, Vec) { - let full_skylight = &self.sky_light; - let half_len = full_skylight.len() / 2; - let mut top = Vec::with_capacity(half_len); - let mut bottom = Vec::with_capacity(half_len); - for i in 0..half_len { - top.push(full_skylight[i * 2]); - bottom.push(full_skylight[i * 2 + 1]); - } - (top, bottom) - } -} - -pub struct CachedLceChunk { - pub chunk_x: i32, - pub chunk_z: i32, - builder: LceChunkBuilder, -} - -impl CachedLceChunk { - pub fn new(chunk_x: i32, chunk_z: i32) -> Self { - Self { chunk_x, chunk_z, builder: LceChunkBuilder::new() } - } - - pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: u16) { - self.builder.set_block(x, y, z, block); - } - - pub fn snapshot_builder(&self) -> LceChunkBuilder { - self.builder.snapshot() - } -} - -pub fn translate_java_chunk( - _chunk_x: i32, - _chunk_z: i32, - chunk_data: &[u8], -) -> (LceChunkBuilder, Vec) { - let mut builder = LceChunkBuilder::new(); - let mut offset: usize = 0; - for section_y in 0..SECTIONS { - if offset >= chunk_data.len() { break; } - if offset + 2 > chunk_data.len() { break; } - let block_count = i16::from_be_bytes([chunk_data[offset], chunk_data[offset + 1]]); - offset += 2; - if block_count == 0 { - if offset >= chunk_data.len() { break; } - let _bits_per_entry = chunk_data[offset]; - offset += 1; - if offset >= chunk_data.len() { break; } - let (palette_len, used) = read_varint_offset(chunk_data, offset); - offset = used; - offset += palette_len as usize * 4; - if offset >= chunk_data.len() { break; } - let (data_len, used2) = read_varint_offset(chunk_data, offset); - offset = used2; - offset += data_len as usize; - continue; - } - if offset >= chunk_data.len() { break; } - let bits_per_entry = chunk_data[offset]; - offset += 1; - if bits_per_entry == 0 || bits_per_entry > 14 { - if offset + 4 <= chunk_data.len() { - offset += 4; - } - if offset >= chunk_data.len() { break; } - let (data_len, used) = read_varint_offset(chunk_data, offset); - offset = used; - offset += data_len as usize; - continue; - } - let (palette_len, used) = read_varint_offset(chunk_data, offset); - offset = used; - let mut palette = Vec::with_capacity(palette_len as usize); - for _ in 0..palette_len { - if offset + 4 > chunk_data.len() { break; } - palette.push(i32::from_be_bytes([ - chunk_data[offset], chunk_data[offset+1], chunk_data[offset+2], chunk_data[offset+3] - ])); - offset += 4; - } - if offset >= chunk_data.len() { break; } - let (data_len, used2) = read_varint_offset(chunk_data, offset); - offset = used2; - if offset + data_len as usize > chunk_data.len() { break; } - let compact_data = &chunk_data[offset..offset + data_len as usize]; - offset += data_len as usize; - let values_per_long = 64 / bits_per_entry as usize; - let mask = (1u64 << bits_per_entry) - 1; - for i in 0..4096usize { - let long_idx = i / values_per_long; - let bit_offset = (i % values_per_long) * bits_per_entry as usize; - if long_idx >= compact_data.len() / 8 { continue; } - let mut val = 0u64; - for b in 0..8 { - let byte_idx = long_idx * 8 + b; - if byte_idx < compact_data.len() { - val |= (compact_data[byte_idx] as u64) << (b * 8); - } - } - let palette_idx = ((val >> bit_offset) & mask) as usize; - let java_state = palette.get(palette_idx).copied().unwrap_or(0); - let bx = i & 0xF; - let by = (i >> 8) & 0xF; - let bz = (i >> 4) & 0xF; - let world_y = section_y * 16 + by; - let lce_block = registry::registry().get_lce(java_state); - builder.set_block(bx, world_y, bz, lce_block); - } - } - let raw_data = builder.build_raw_data(); - let compressed = compress_rle_zlib(&raw_data); - (builder, compressed) -} - -pub fn compress_rle_zlib(data: &[u8]) -> Vec { - let rle_encoded = rle_encode(data); - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - let _ = encoder.write_all(&rle_encoded); - encoder.finish().unwrap_or_else(|_| rle_encoded) -} - -fn rle_encode(data: &[u8]) -> Vec { - let mut out = Vec::with_capacity(data.len()); - let mut i = 0; - while i < data.len() { - let byte = data[i]; - if byte == 0xFF || (i + 1 < data.len() && data[i + 1] == byte) { - let mut count = 1usize; - while i + count < data.len() && data[i + count] == byte && count < 255 { - count += 1; - } - out.push(0xFF); - out.push((count - 1) as u8); - out.push(byte); - i += count; - } else { - out.push(byte); - i += 1; - } - } - out -} - -fn read_varint_offset(data: &[u8], offset: usize) -> (i32, usize) { - let mut result = 0i32; - let mut shift = 0; - let mut pos = offset; - while pos < data.len() { - let byte = data[pos]; - pos += 1; - result |= ((byte & 0x7F) as i32) << shift; - shift += 7; - if byte & 0x80 == 0 { - return (result, pos); - } - if shift >= 35 { break; } - } - (result, pos) -} diff --git a/src-tauri/src/lce_bridge/config.rs b/src-tauri/src/lce_bridge/config.rs deleted file mode 100644 index e5b5d86..0000000 --- a/src-tauri/src/lce_bridge/config.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BridgeConfig { - pub lce: LceConfig, - pub remote: RemoteConfig, - pub world: WorldConfig, - pub performance: PerformanceConfig, - pub logging: LoggingConfig, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LceConfig { - pub listen_address: String, - pub port: u16, - pub motd: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RemoteConfig { - pub host: String, - pub port: u16, - pub auth_type: String, - pub protocol_version: i32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WorldConfig { - pub render_distance: i32, - pub simulation_distance: i32, - pub force_gamemode: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PerformanceConfig { - pub chunk_threads: usize, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LoggingConfig { - pub level: String, -} - -impl Default for BridgeConfig { - fn default() -> Self { - Self { - lce: LceConfig { - listen_address: "0.0.0.0".to_string(), - port: 25656, - motd: "LCEBridge".to_string(), - }, - remote: RemoteConfig { - host: "127.0.0.1".to_string(), - port: 25565, - auth_type: "offline".to_string(), - protocol_version: 47, - }, - world: WorldConfig { - render_distance: 8, - simulation_distance: 8, - force_gamemode: None, - }, - performance: PerformanceConfig { - chunk_threads: 2, - }, - logging: LoggingConfig { - level: "info".to_string(), - }, - } - } -} diff --git a/src-tauri/src/lce_bridge/crafting.rs b/src-tauri/src/lce_bridge/crafting.rs deleted file mode 100644 index b19552a..0000000 --- a/src-tauri/src/lce_bridge/crafting.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug, Clone)] -pub struct LegacyRecipe { - pub index: u8, - pub input: Vec>, - pub output: (ItemSpec, u8), - pub width: u8, - pub height: u8, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct IngredientKey { - pub item_id: i16, - pub data: i16, - pub accept_any_data: bool, -} - -#[derive(Debug, Clone)] -pub struct ItemSpec { - pub id: i16, - pub data: i16, -} - -pub struct RecipeCatalog { - pub recipes: Vec, - by_index: HashMap, -} - -impl RecipeCatalog { - pub fn new() -> Self { - let recipes = build_catalog(); - let mut by_index = HashMap::new(); - for (i, r) in recipes.iter().enumerate() { - by_index.insert(r.index, i); - } - Self { recipes, by_index } - } - - pub fn recipe_for_index(&self, index: u8) -> Option<&LegacyRecipe> { - self.by_index.get(&index).map(|&i| &self.recipes[i]) - } -} - -fn build_catalog() -> Vec { - let mut recipes = Vec::new(); - recipes.push(LegacyRecipe { - index: 0, width: 1, height: 1, - input: vec![Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true })], - output: (ItemSpec { id: 281, data: 0 }, 4), - }); - recipes.push(LegacyRecipe { - index: 1, width: 2, height: 1, - input: vec![ - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 280, data: 0 }, 4), - }); - recipes.push(LegacyRecipe { - index: 2, width: 2, height: 2, - input: vec![ - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 5, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 3, width: 3, height: 3, - input: vec![ - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 4, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 4, width: 2, height: 1, - input: vec![ - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 256, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 5, width: 3, height: 2, - input: vec![ - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - None, - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - None, - ], - output: (ItemSpec { id: 257, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 6, width: 3, height: 3, - input: vec![ - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - None, - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - None, - None, - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - None, - ], - output: (ItemSpec { id: 258, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 7, width: 2, height: 2, - input: vec![ - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 265, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 266, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 8, width: 1, height: 1, - input: vec![ - Some(IngredientKey { item_id: 263, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 263, data: 1 }, 1), - }); - recipes.push(LegacyRecipe { - index: 9, width: 3, height: 2, - input: vec![ - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - None, - Some(IngredientKey { item_id: 280, data: 0, accept_any_data: true }), - None, - ], - output: (ItemSpec { id: 290, data: 0 }, 1), - }); - recipes.push(LegacyRecipe { - index: 10, width: 3, height: 1, - input: vec![ - Some(IngredientKey { item_id: 336, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 336, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 336, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 30, data: 0 }, 6), - }); - recipes.push(LegacyRecipe { - index: 11, width: 1, height: 1, - input: vec![ - Some(IngredientKey { item_id: 337, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 339, data: 0 }, 4), - }); - recipes.push(LegacyRecipe { - index: 12, width: 1, height: 1, - input: vec![ - Some(IngredientKey { item_id: 338, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 339, data: 0 }, 9), - }); - recipes.push(LegacyRecipe { - index: 13, width: 3, height: 3, - input: vec![ - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - None, - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 3, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 17, data: 0 }, 3), - }); - recipes.push(LegacyRecipe { - index: 14, width: 2, height: 1, - input: vec![ - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - Some(IngredientKey { item_id: 5, data: 0, accept_any_data: true }), - ], - output: (ItemSpec { id: 280, data: 0 }, 4), - }); - recipes -} - -pub struct InventoryManager { - pub slots: Vec, - pub catalog: RecipeCatalog, -} - -use crate::lce_bridge::lce_packets::LceItemStack; - -impl InventoryManager { - pub fn new() -> Self { - Self { - slots: Vec::new(), - catalog: RecipeCatalog::new(), - } - } - - pub fn set_slots(&mut self, slots: Vec) { - self.slots = slots; - } - - pub fn can_craft(&self, recipe_index: u8, player_inventory: &[LceItemStack]) -> bool { - if let Some(recipe) = self.catalog.recipe_for_index(recipe_index) { - let mut needed: HashMap<(i16, i16), u8> = HashMap::new(); - for ing in &recipe.input { - if let Some(key) = ing { - *needed.entry((key.item_id, key.data)).or_insert(0) += 1; - } - } - let mut available: HashMap<(i16, i16), u8> = HashMap::new(); - for item in player_inventory { - if item.id > 0 && item.count > 0 { - *available.entry((item.id, item.damage)).or_insert(0) += item.count; - } - } - for ((id, data), count) in &needed { - let avail = available.get(&(*id, *data)).copied().unwrap_or(0); - if avail < *count { return false; } - } - true - } else { - false - } - } -} diff --git a/src-tauri/src/lce_bridge/java_protocol.rs b/src-tauri/src/lce_bridge/java_protocol.rs deleted file mode 100644 index a21ae11..0000000 --- a/src-tauri/src/lce_bridge/java_protocol.rs +++ /dev/null @@ -1,1066 +0,0 @@ -use crate::lce_bridge::util::*; - -use std::io::Read; - -pub struct JavaConnection { - stream: tokio::net::TcpStream, - compression_threshold: i32, - pub state: JavaProtocolState, -} - -#[derive(Debug)] -pub enum JavaProtocolState { - Handshaking, - Login, - Config, - Play, -} - -impl JavaConnection { - pub async fn connect(host: &str, port: u16) -> std::io::Result { - let stream = tokio::net::TcpStream::connect((host, port)).await?; - Ok(Self { - stream, - compression_threshold: -1, - state: JavaProtocolState::Handshaking, - }) - } - - pub async fn send_packet(&mut self, packet: &JavaPacket) -> std::io::Result<()> { - use tokio::io::AsyncWriteExt; - let mut payload = Vec::new(); - packet.encode(&mut payload)?; - let mut frame = Vec::new(); - frame.extend_from_slice(&VarInt::write(packet.id())); - if self.compression_threshold >= 0 { - if payload.len() as i32 > self.compression_threshold { - use flate2::write::ZlibEncoder; - use flate2::Compression; - use std::io::Write; - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(&payload)?; - let compressed = encoder.finish()?; - let mut compressed_frame = Vec::new(); - compressed_frame.extend_from_slice(&VarInt::write(payload.len() as i32)); - compressed_frame.extend_from_slice(&compressed); - payload = compressed_frame; - } else { - let mut uncompressed_frame = Vec::new(); - uncompressed_frame.extend_from_slice(&VarInt::write(0)); - uncompressed_frame.extend_from_slice(&payload); - payload = uncompressed_frame; - } - } - frame.extend_from_slice(&payload); - let mut len_prefix = VarInt::write(frame.len() as i32); - len_prefix.extend_from_slice(&frame); - self.stream.write_all(&len_prefix).await?; - Ok(()) - } - - pub async fn read_packet(&mut self) -> std::io::Result { - use tokio::io::AsyncReadExt; - let len = read_varint_stream(&mut self.stream).await?; - let mut packet_data = vec![0u8; len as usize]; - self.stream.read_exact(&mut packet_data).await?; - let decompressed = if self.compression_threshold >= 0 { - let mut data = &packet_data[..]; - let uncompressed_len = VarInt::read(&mut data)?; - if uncompressed_len > 0 { - use flate2::read::ZlibDecoder; - use std::io::Read; - let mut decoder = ZlibDecoder::new(data); - let mut buf = Vec::with_capacity(uncompressed_len as usize); - decoder.read_to_end(&mut buf)?; - buf - } else { - data.to_vec() - } - } else { - packet_data.clone() - }; - let mut data = &decompressed[..]; - let packet_id = VarInt::read(&mut data)?; - JavaPacket::decode(packet_id, data, &self.state) - } -} - -async fn read_varint_stream(stream: &mut tokio::net::TcpStream) -> std::io::Result { - use tokio::io::AsyncReadExt; - let mut result = 0i32; - let mut shift = 0; - loop { - let byte = stream.read_u8().await?; - result |= ((byte & 0x7F) as i32) << shift; - shift += 7; - if byte & 0x80 == 0 { - return Ok(result); - } - if shift >= 35 { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "varint too long")); - } - } -} - -#[derive(Debug, Clone)] -pub enum JavaPacket { - Handshake(HandshakePacket), - LoginStart(LoginStartPacket), - LoginSuccess(LoginSuccessPacket), - LoginAcknowledged, - SetCompression(SetCompressionPacket), - FinishConfig, - KeepAliveC2S(KeepAliveC2SPacket), - KeepAliveS2C(KeepAliveS2CPacket), - ChunkData(ChunkDataPacket), - ChunkBatchFinished, - RegistryData(RegistryDataPacket), - PlayerPosition(PlayerPositionPacket), - AcceptTeleportation(AcceptTeleportationPacket), - ClientInformation(ClientInformationPacket), - SynchronizePlayerPosition(SynchronizePlayerPositionPacket), - SetContainerContent(SetContainerContentPacket), - SetContainerSlot(SetContainerSlotPacket), - OpenScreen(OpenScreenPacket), - SystemChat(SystemChatPacket), - PlayerChat(PlayerChatPacket), - ChatCommand(ChatCommandPacket), - SetHealth(SetHealthS2CPacket), - SetTime(SetTimeS2CPacket), - AddEntity(AddEntityS2CPacket), - AddMob(AddMobS2CPacket), - AddPlayer(AddPlayerS2CPacket), - RemoveEntities(RemoveEntitiesS2CPacket), - SetEntityMotion(SetEntityMotionS2CPacket), - TeleportEntity(TeleportEntityS2CPacket), - SetHeadRotation(SetHeadRotationS2CPacket), - SetEntityData(SetEntityDataS2CPacket), - LoginPlay(LoginPlayPacket), - SoundEffect(SoundEffectS2CPacket), - SetDefaultSpawnPosition(SetDefaultSpawnPositionS2CPacket), - GameEventS2C(GameEventS2CPacket), - ContainerCloseS2C(ContainerCloseS2CPacket), - ContainerSetDataS2C(ContainerSetDataS2CPacket), - ContainerAckS2C(ContainerAckS2CPacket), - PingPong(PingPongPacket), - EncryptionRequest(EncryptionRequestPacket), - EncryptionResponse(EncryptionResponsePacket), - Unknown { id: i32, data: Vec }, -} - -impl JavaPacket { - pub fn id(&self) -> i32 { - use JavaPacket::*; - match self { - Handshake(_) => 0x00, - LoginStart(_) => 0x00, - LoginSuccess(_) => 0x03, - LoginAcknowledged => 0x03, - SetCompression(_) => 0x03, - FinishConfig => 0x03, - KeepAliveC2S(_) => 0x18, - KeepAliveS2C(_) => 0x26, - ChunkData(_) => 0x24, - ChunkBatchFinished => 0x71, - RegistryData(_) => 0x7C, - PlayerPosition(_) => 0x40, - AcceptTeleportation(_) => 0x00, - ClientInformation(_) => 0x09, - SynchronizePlayerPosition(_) => 0x3E, - SetContainerContent(_) => 0x13, - SetContainerSlot(_) => 0x14, - OpenScreen(_) => 0x32, - SystemChat(_) => 0x6F, - PlayerChat(_) => 0x37, - ChatCommand(_) => 0x04, - SetHealth(_) => 0x5A, - SetTime(_) => 0x5C, - AddEntity(_) => 0x01, - AddMob(_) => 0x03, - AddPlayer(_) => 0x04, - RemoveEntities(_) => 0x41, - SetEntityMotion(_) => 0x2E, - TeleportEntity(_) => 0x6E, - SetHeadRotation(_) => 0x4A, - SetEntityData(_) => 0x4C, - LoginPlay(_) => 0x2C, - SoundEffect(_) => 0x64, - SetDefaultSpawnPosition(_) => 0x5B, - GameEventS2C(_) => 0x1F, - ContainerCloseS2C(_) => 0x12, - ContainerSetDataS2C(_) => 0x15, - ContainerAckS2C(_) => 0x07, - EncryptionRequest(_) => 0x01, - EncryptionResponse(_) => 0x01, - PingPong(_) => 0x26, - Unknown { id, .. } => *id, - } - } - - pub fn encode(&self, buf: &mut Vec) -> std::io::Result<()> { - use JavaPacket::*; - match self { - Handshake(p) => { - VarInt::write_to(p.protocol_version, buf); - write_utf8_string(buf, &p.server_address); - buf.extend_from_slice(&p.server_port.to_be_bytes()); - VarInt::write_to(p.next_state, buf); - } - LoginStart(p) => { - write_utf8_string(buf, &p.username); - if p.protocol_version >= 761 && p.protocol_version <= 765 { - buf.push(0); - } - if p.protocol_version >= 761 { - buf.extend_from_slice(p.profile_id.as_bytes()); - } - } - LoginSuccess(p) => { - write_uuid(buf, &p.uuid); - write_utf8_string(buf, &p.username); - VarInt::write_to(0, buf); - } - LoginAcknowledged => {} - SetCompression(p) => { VarInt::write_to(p.threshold, buf); } - FinishConfig => {} - KeepAliveC2S(p) => { buf.extend_from_slice(&p.id.to_be_bytes()); } - KeepAliveS2C(p) => { buf.extend_from_slice(&p.id.to_be_bytes()); } - ClientInformation(p) => { - write_utf8_string(buf, &p.locale); - buf.push(p.view_distance); - VarInt::write_to(p.chat_mode, buf); - buf.push(p.chat_colors as u8); - buf.push(p.displayed_skin_parts); - VarInt::write_to(p.main_hand, buf); - buf.push(p.enable_text_filtering as u8); - buf.push(p.allow_listing as u8); - VarInt::write_to(p.particle_status, buf); - } - AcceptTeleportation(p) => { VarInt::write_to(p.teleport_id, buf); } - PingPong(p) => { buf.extend_from_slice(&p.id.to_be_bytes()); } - EncryptionRequest(p) => { - write_utf8_string(buf, &p.server_id); - VarInt::write_to(p.public_key.len() as i32, buf); - buf.extend_from_slice(&p.public_key); - VarInt::write_to(p.verify_token.len() as i32, buf); - buf.extend_from_slice(&p.verify_token); - } - EncryptionResponse(p) => { - VarInt::write_to(p.shared_secret.len() as i32, buf); - buf.extend_from_slice(&p.shared_secret); - VarInt::write_to(p.verify_token.len() as i32, buf); - buf.extend_from_slice(&p.verify_token); - } - _ => { - if let Unknown { data, .. } = self { - buf.extend_from_slice(data); - } - } - } - Ok(()) - } - - pub fn decode(id: i32, data: &[u8], state: &JavaProtocolState) -> std::io::Result { - let mut r = data; - Ok(match (state, id) { - (JavaProtocolState::Login, 0x00) => { - let reason = read_utf8_string(&mut r)?; - eprintln!("[LCEBridge] Java server disconnected: {}", reason); - JavaPacket::Unknown { id, data: data.to_vec() } - } - (JavaProtocolState::Login, 0x01) => { - let server_id = read_utf8_string(&mut r)?; - let key_len = VarInt::read(&mut r)? as usize; - let mut public_key = vec![0u8; key_len]; - r.read_exact(&mut public_key)?; - let token_len = VarInt::read(&mut r)? as usize; - let mut verify_token = vec![0u8; token_len]; - r.read_exact(&mut verify_token)?; - JavaPacket::EncryptionRequest(EncryptionRequestPacket { - server_id, public_key, verify_token, - }) - } - (JavaProtocolState::Login, 0x02) => { - let saved = data; - let mut r2 = saved; - if let Ok(uuid) = read_uuid(&mut r2) { - if let Ok(username) = read_utf8_string(&mut r2) { - return Ok(JavaPacket::LoginSuccess(LoginSuccessPacket { uuid, username })); - } - } - let uuid_str = read_utf8_string(&mut r)?; - let uuid = uuid::Uuid::try_parse(&uuid_str).unwrap_or(uuid::Uuid::nil()); - let username = read_utf8_string(&mut r)?; - JavaPacket::LoginSuccess(LoginSuccessPacket { uuid, username }) - } - (JavaProtocolState::Login, 0x03) => { - let peek = r.first().copied().unwrap_or(0); - if (peek & 0x80) != 0 || peek >= 4 { - let threshold = VarInt::read(&mut r)?; - JavaPacket::SetCompression(SetCompressionPacket { threshold }) - } else { - let uuid = read_uuid(&mut r)?; - let username = read_utf8_string(&mut r)?; - JavaPacket::LoginSuccess(LoginSuccessPacket { uuid, username }) - } - } - (JavaProtocolState::Config, 0x03) => { - JavaPacket::FinishConfig - } - (JavaProtocolState::Play, 0x26) => { - let id_val = read_i64be(&mut r)?; - JavaPacket::KeepAliveS2C(KeepAliveS2CPacket { id: id_val }) - } - (JavaProtocolState::Play, 0x24) => { - let chunk_x = read_i32be(&mut r)?; - let chunk_z = read_i32be(&mut r)?; - let _heightmaps = read_nbt(&mut r)?; - let data_size = VarInt::read(&mut r)? as usize; - let mut chunk_data = vec![0u8; data_size]; - r.read_exact(&mut chunk_data)?; - let block_entities_count = VarInt::read(&mut r)?; - for _ in 0..block_entities_count { - let _packed_xyz = read_u8be(&mut r)?; - let _block_entity_type = VarInt::read(&mut r)?; - let _nbt = read_nbt(&mut r)?; - } - let _trust_edges = read_u8be(&mut r)? != 0; - let _sky_light_mask = read_varint_array(&mut r)?; - let _block_light_mask = read_varint_array(&mut r)?; - let _empty_sky_light_mask = read_varint_array(&mut r)?; - let _empty_block_light_mask = read_varint_array(&mut r)?; - let sky_light_arrays_count = VarInt::read(&mut r)?; - let mut light_data = Vec::new(); - for _ in 0..sky_light_arrays_count { - let light_len = VarInt::read(&mut r)? as usize; - let mut light = vec![0u8; light_len]; - r.read_exact(&mut light)?; - light_data.extend_from_slice(&light); - } - let block_light_arrays_count = VarInt::read(&mut r)?; - for _ in 0..block_light_arrays_count { - let light_len = VarInt::read(&mut r)? as usize; - let mut light = vec![0u8; light_len]; - r.read_exact(&mut light)?; - } - JavaPacket::ChunkData(ChunkDataPacket { - chunk_x, chunk_z, chunk_data, light_data, - trust_edges: false, block_entities: Vec::new(), - }) - } - (JavaProtocolState::Play, 0x71) => { - JavaPacket::ChunkBatchFinished - } - (JavaProtocolState::Config, 0x07) => { - let _registry_codec = read_nbt(&mut r)?; - JavaPacket::RegistryData(RegistryDataPacket {}) - } - (JavaProtocolState::Play, 0x40) => { - let teleport_id = VarInt::read(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_f32be(&mut r)?; - let pitch = read_f32be(&mut r)?; - let _flags = read_u8be(&mut r)?; - let _ = VarInt::read(&mut r)?; - JavaPacket::PlayerPosition(PlayerPositionPacket { - teleport_id, x, y, z, yaw, pitch, flags: _flags, - }) - } - (JavaProtocolState::Play, 0x3E) => { - let teleport_id = VarInt::read(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_f32be(&mut r)?; - let pitch = read_f32be(&mut r)?; - let _dismount_vehicle = read_u8be(&mut r)?; - let _ = VarInt::read(&mut r)?; - JavaPacket::SynchronizePlayerPosition(SynchronizePlayerPositionPacket { - teleport_id, x, y, z, yaw, pitch, - }) - } - (JavaProtocolState::Play, 0x13) => { - let container_id = VarInt::read(&mut r)?; - let _state_id = VarInt::read(&mut r)?; - let slots_count = VarInt::read(&mut r)? as usize; - let mut slots = Vec::with_capacity(slots_count); - for _ in 0..slots_count { - slots.push(read_java_slot(&mut r)?); - } - let _carried_item = read_java_slot(&mut r)?; - JavaPacket::SetContainerContent(SetContainerContentPacket { - container_id, state_id: _state_id, slots, - }) - } - (JavaProtocolState::Play, 0x14) => { - let container_id = VarInt::read(&mut r)?; - let _state_id = VarInt::read(&mut r)?; - let slot = i16::try_from(VarInt::read(&mut r)?).unwrap_or(0); - let item = read_java_slot(&mut r)?; - JavaPacket::SetContainerSlot(SetContainerSlotPacket { - container_id, state_id: _state_id, slot, item, - }) - } - (JavaProtocolState::Play, 0x32) => { - let container_id = VarInt::read(&mut r)?; - let _screen_id = VarInt::read(&mut r)?; - let _title = read_nbt(&mut r)?; - JavaPacket::OpenScreen(OpenScreenPacket { - container_id, screen_id: _screen_id, title: _title, - }) - } - (JavaProtocolState::Play, 0x6F) => { - let content = read_nbt(&mut r)?; - let is_actionbar = read_u8be(&mut r)? != 0; - JavaPacket::SystemChat(SystemChatPacket { - content_json: content, is_actionbar, - }) - } - (JavaProtocolState::Play, 0x37) => { - let content = read_nbt(&mut r)?; - let _position = read_i64be(&mut r)?; - let mut uuid_bytes = [0u8; 16]; - r.read_exact(&mut uuid_bytes)?; - let sender = uuid::Uuid::from_bytes(uuid_bytes); - JavaPacket::PlayerChat(PlayerChatPacket { - content_json: content, sender, - }) - } - (JavaProtocolState::Play, 0x5A) => { - let health = read_f32be(&mut r)?; - let food = VarInt::read(&mut r)?; - let saturation = read_f32be(&mut r)?; - JavaPacket::SetHealth(SetHealthS2CPacket { health, food, saturation }) - } - (JavaProtocolState::Play, 0x5C) => { - let world_age = read_i64be(&mut r)?; - let day_time = read_i64be(&mut r)?; - JavaPacket::SetTime(SetTimeS2CPacket { world_age, day_time }) - } - (JavaProtocolState::Play, 0x01) => { - let entity_id = VarInt::read(&mut r)?; - let entity_type = VarInt::read(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_u8be(&mut r)?; - let pitch = read_u8be(&mut r)?; - let head_yaw = read_u8be(&mut r)?; - let data = VarInt::read(&mut r)?; - let vel_x = read_i16be(&mut r)?; - let vel_y = read_i16be(&mut r)?; - let vel_z = read_i16be(&mut r)?; - JavaPacket::AddEntity(AddEntityS2CPacket { - entity_id, entity_type, x, y, z, yaw, pitch, head_yaw, data, - vel_x, vel_y, vel_z, - }) - } - (JavaProtocolState::Play, 0x03) => { - let entity_id = VarInt::read(&mut r)?; - let _uuid = read_uuid(&mut r)?; - let entity_type = VarInt::read(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_u8be(&mut r)?; - let pitch = read_u8be(&mut r)?; - let head_yaw = read_u8be(&mut r)?; - let vel_x = read_i16be(&mut r)?; - let vel_y = read_i16be(&mut r)?; - let vel_z = read_i16be(&mut r)?; - let _metadata = read_java_metadata(&mut r)?; - JavaPacket::AddMob(AddMobS2CPacket { - entity_id, entity_type, x, y, z, yaw, pitch, head_yaw, - vel_x, vel_y, vel_z, - }) - } - (JavaProtocolState::Play, 0x04) => { - let entity_id = VarInt::read(&mut r)?; - let _uuid = read_uuid(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_u8be(&mut r)?; - let pitch = read_u8be(&mut r)?; - let _metadata = read_java_metadata(&mut r)?; - JavaPacket::AddPlayer(AddPlayerS2CPacket { - entity_id, x, y, z, yaw, pitch, - }) - } - (JavaProtocolState::Play, 0x41) => { - let count = VarInt::read(&mut r)?; - let mut entity_ids = Vec::with_capacity(count as usize); - for _ in 0..count { - entity_ids.push(VarInt::read(&mut r)?); - } - JavaPacket::RemoveEntities(RemoveEntitiesS2CPacket { entity_ids }) - } - (JavaProtocolState::Play, 0x2E) => { - let entity_id = VarInt::read(&mut r)?; - let xa = read_i16be(&mut r)?; - let ya = read_i16be(&mut r)?; - let za = read_i16be(&mut r)?; - JavaPacket::SetEntityMotion(SetEntityMotionS2CPacket { entity_id, xa, ya, za }) - } - (JavaProtocolState::Play, 0x6E) => { - let entity_id = VarInt::read(&mut r)?; - let x = read_f64be(&mut r)?; - let y = read_f64be(&mut r)?; - let z = read_f64be(&mut r)?; - let yaw = read_u8be(&mut r)?; - let pitch = read_u8be(&mut r)?; - let _on_ground = read_u8be(&mut r)? != 0; - JavaPacket::TeleportEntity(TeleportEntityS2CPacket { - entity_id, x, y, z, yaw, pitch, - }) - } - (JavaProtocolState::Play, 0x4A) => { - let entity_id = VarInt::read(&mut r)?; - let head_yaw = read_u8be(&mut r)?; - JavaPacket::SetHeadRotation(SetHeadRotationS2CPacket { entity_id, head_yaw }) - } - (JavaProtocolState::Play, 0x4C) => { - let entity_id = VarInt::read(&mut r)?; - let _metadata = read_java_metadata(&mut r)?; - JavaPacket::SetEntityData(SetEntityDataS2CPacket { entity_id }) - } - (JavaProtocolState::Play, 0x2C) => { - let entity_id = VarInt::read(&mut r)?; - let _is_hardcore = read_u8be(&mut r)? != 0; - let _dim_count = VarInt::read(&mut r)?; - for _ in 0.._dim_count { - let _ = read_utf8_string(&mut r)?; - } - let _max_players = VarInt::read(&mut r)?; - let view_distance = VarInt::read(&mut r)?; - let simulation_distance = VarInt::read(&mut r)?; - let _reduced_debug = read_u8be(&mut r)? != 0; - let _enable_respawn_screen = read_u8be(&mut r)? != 0; - let _do_limited_crafting = read_u8be(&mut r)? != 0; - let _dim_type = VarInt::read(&mut r)?; - let _dim_name = read_utf8_string(&mut r)?; - let _hashed_seed = read_i64be(&mut r)?; - let _ = VarInt::read(&mut r)?; - let _is_debug = read_u8be(&mut r)? != 0; - let _is_flat = read_u8be(&mut r)? != 0; - JavaPacket::LoginPlay(LoginPlayPacket { - entity_id, is_hardcore: _is_hardcore, - view_distance, simulation_distance, - reduced_debug: _reduced_debug, - enable_respawn_screen: _enable_respawn_screen, - do_limited_crafting: _do_limited_crafting, - hashed_seed: _hashed_seed, - is_debug: _is_debug, - is_flat: _is_flat, - }) - } - (JavaProtocolState::Play, 0x64) => { - let _sound_id = VarInt::read(&mut r)?; - let _sound_category = VarInt::read(&mut r)?; - let _x = read_i32be(&mut r)?; - let _y = read_i32be(&mut r)?; - let _z = read_i32be(&mut r)?; - let _volume = read_f32be(&mut r)?; - let _pitch = read_f32be(&mut r)?; - let _seed = read_i64be(&mut r)?; - JavaPacket::SoundEffect(SoundEffectS2CPacket { - sound_id: _sound_id, sound_category: _sound_category, - x: _x, y: _y, z: _z, volume: _volume, pitch: _pitch, seed: _seed, - }) - } - (JavaProtocolState::Play, 0x5B) => { - let x = read_i32be(&mut r)?; - let y = read_i32be(&mut r)?; - let z = read_i32be(&mut r)?; - let _angle = read_f32be(&mut r)?; - JavaPacket::SetDefaultSpawnPosition(SetDefaultSpawnPositionS2CPacket { x, y, z }) - } - (JavaProtocolState::Play, 0x1F) => { - let reason = read_u8be(&mut r)?; - let param = read_f32be(&mut r)?; - JavaPacket::GameEventS2C(GameEventS2CPacket { reason, param }) - } - (JavaProtocolState::Play, 0x12) => { - let container_id = VarInt::read(&mut r)?; - JavaPacket::ContainerCloseS2C(ContainerCloseS2CPacket { container_id }) - } - (JavaProtocolState::Play, 0x15) => { - let container_id = VarInt::read(&mut r)?; - let key = VarInt::read(&mut r)?; - let value = VarInt::read(&mut r)?; - JavaPacket::ContainerSetDataS2C(ContainerSetDataS2CPacket { container_id, key, value }) - } - (JavaProtocolState::Play, 0x07) => { - let container_id = read_u8be(&mut r)?; - let uid = read_i16be(&mut r)?; - let accepted = read_u8be(&mut r)? != 0; - JavaPacket::ContainerAckS2C(ContainerAckS2CPacket { container_id, uid, accepted }) - } - _ => { - JavaPacket::Unknown { id, data: data.to_vec() } - } - }) - } -} - -#[derive(Debug, Clone)] -pub struct HandshakePacket { - pub protocol_version: i32, - pub server_address: String, - pub server_port: u16, - pub next_state: i32, -} - -#[derive(Debug, Clone)] -pub struct LoginStartPacket { - pub username: String, - pub profile_id: uuid::Uuid, - pub protocol_version: i32, -} - -#[derive(Debug, Clone)] -pub struct LoginSuccessPacket { - pub uuid: uuid::Uuid, - pub username: String, -} - -#[derive(Debug, Clone)] -pub struct SetCompressionPacket { - pub threshold: i32, -} - -#[derive(Debug, Clone)] -pub struct KeepAliveC2SPacket { - pub id: i64, -} - -#[derive(Debug, Clone)] -pub struct KeepAliveS2CPacket { - pub id: i64, -} - -#[derive(Debug, Clone)] -pub struct ChunkDataPacket { - pub chunk_x: i32, - pub chunk_z: i32, - pub chunk_data: Vec, - pub light_data: Vec, - pub trust_edges: bool, - pub block_entities: Vec<()>, -} - -#[derive(Debug, Clone)] -pub struct RegistryDataPacket {} - -#[derive(Debug, Clone)] -pub struct PlayerPositionPacket { - pub teleport_id: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: f32, pub pitch: f32, - pub flags: u8, -} - -#[derive(Debug, Clone)] -pub struct AcceptTeleportationPacket { - pub teleport_id: i32, -} - -#[derive(Debug, Clone)] -pub struct ClientInformationPacket { - pub locale: String, - pub view_distance: u8, - pub chat_mode: i32, - pub chat_colors: bool, - pub displayed_skin_parts: u8, - pub main_hand: i32, - pub enable_text_filtering: bool, - pub allow_listing: bool, - pub particle_status: i32, -} - -#[derive(Debug, Clone)] -pub struct SynchronizePlayerPositionPacket { - pub teleport_id: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: f32, pub pitch: f32, -} - -#[derive(Debug, Clone)] -pub struct SetContainerContentPacket { - pub container_id: i32, - pub state_id: i32, - pub slots: Vec, -} - -#[derive(Debug, Clone)] -pub struct SetContainerSlotPacket { - pub container_id: i32, - pub state_id: i32, - pub slot: i16, - pub item: JavaSlot, -} - -#[derive(Debug, Clone)] -pub struct OpenScreenPacket { - pub container_id: i32, - pub screen_id: i32, - pub title: Vec, -} - -#[derive(Debug, Clone)] -pub struct SystemChatPacket { - pub content_json: Vec, - pub is_actionbar: bool, -} - -#[derive(Debug, Clone)] -pub struct PlayerChatPacket { - pub content_json: Vec, - pub sender: uuid::Uuid, -} - -#[derive(Debug, Clone)] -pub struct ChatCommandPacket { - pub command: String, -} - -#[derive(Debug, Clone)] -pub struct SetHealthS2CPacket { - pub health: f32, - pub food: i32, - pub saturation: f32, -} - -#[derive(Debug, Clone)] -pub struct SetTimeS2CPacket { - pub world_age: i64, - pub day_time: i64, -} - -#[derive(Debug, Clone)] -pub struct AddEntityS2CPacket { - pub entity_id: i32, - pub entity_type: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: u8, pub pitch: u8, pub head_yaw: u8, - pub data: i32, - pub vel_x: i16, pub vel_y: i16, pub vel_z: i16, -} - -#[derive(Debug, Clone)] -pub struct AddMobS2CPacket { - pub entity_id: i32, - pub entity_type: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: u8, pub pitch: u8, pub head_yaw: u8, - pub vel_x: i16, pub vel_y: i16, pub vel_z: i16, -} - -#[derive(Debug, Clone)] -pub struct AddPlayerS2CPacket { - pub entity_id: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: u8, pub pitch: u8, -} - -#[derive(Debug, Clone)] -pub struct RemoveEntitiesS2CPacket { - pub entity_ids: Vec, -} - -#[derive(Debug, Clone)] -pub struct SetEntityMotionS2CPacket { - pub entity_id: i32, - pub xa: i16, pub ya: i16, pub za: i16, -} - -#[derive(Debug, Clone)] -pub struct TeleportEntityS2CPacket { - pub entity_id: i32, - pub x: f64, pub y: f64, pub z: f64, - pub yaw: u8, pub pitch: u8, -} - -#[derive(Debug, Clone)] -pub struct SetHeadRotationS2CPacket { - pub entity_id: i32, - pub head_yaw: u8, -} - -#[derive(Debug, Clone)] -pub struct SetEntityDataS2CPacket { - pub entity_id: i32, -} - -#[derive(Debug, Clone)] -pub struct LoginPlayPacket { - pub entity_id: i32, - pub is_hardcore: bool, - pub view_distance: i32, - pub simulation_distance: i32, - pub reduced_debug: bool, - pub enable_respawn_screen: bool, - pub do_limited_crafting: bool, - pub hashed_seed: i64, - pub is_debug: bool, - pub is_flat: bool, -} - -#[derive(Debug, Clone)] -pub struct SoundEffectS2CPacket { - pub sound_id: i32, - pub sound_category: i32, - pub x: i32, pub y: i32, pub z: i32, - pub volume: f32, - pub pitch: f32, - pub seed: i64, -} - -#[derive(Debug, Clone)] -pub struct SetDefaultSpawnPositionS2CPacket { - pub x: i32, pub y: i32, pub z: i32, -} - -#[derive(Debug, Clone)] -pub struct GameEventS2CPacket { - pub reason: u8, - pub param: f32, -} - -#[derive(Debug, Clone)] -pub struct ContainerCloseS2CPacket { - pub container_id: i32, -} - -#[derive(Debug, Clone)] -pub struct ContainerSetDataS2CPacket { - pub container_id: i32, - pub key: i32, - pub value: i32, -} - -#[derive(Debug, Clone)] -pub struct ContainerAckS2CPacket { - pub container_id: u8, - pub uid: i16, - pub accepted: bool, -} - -#[derive(Debug, Clone)] -pub struct PingPongPacket { - pub id: i64, -} - -#[derive(Debug, Clone)] -pub struct EncryptionRequestPacket { - pub server_id: String, - pub public_key: Vec, - pub verify_token: Vec, -} - -#[derive(Debug, Clone)] -pub struct EncryptionResponsePacket { - pub shared_secret: Vec, - pub verify_token: Vec, -} - -#[derive(Debug, Clone)] -pub struct JavaSlot { - pub present: bool, - pub item_id: i32, - pub count: u8, - pub components: Vec, -} - -impl JavaSlot { - pub fn empty() -> Self { - Self { present: false, item_id: 0, count: 0, components: Vec::new() } - } -} - -fn write_uuid(buf: &mut Vec, uuid: &uuid::Uuid) { - buf.extend_from_slice(uuid.as_bytes()); -} - -pub fn write_utf8_string(buf: &mut Vec, s: &str) { - let bytes = s.as_bytes(); - VarInt::write_to(bytes.len() as i32, buf); - buf.extend_from_slice(bytes); -} - -fn read_uuid(r: &mut &[u8]) -> std::io::Result { - let mut bytes = [0u8; 16]; - r.read_exact(&mut bytes)?; - Ok(uuid::Uuid::from_bytes(bytes)) -} - -fn read_u8be(r: &mut &[u8]) -> std::io::Result { - if r.is_empty() { return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")); } - let v = r[0]; - *r = &r[1..]; - Ok(v) -} - -fn read_i16be(r: &mut &[u8]) -> std::io::Result { - let mut buf = [0u8; 2]; - r.read_exact(&mut buf)?; - Ok(i16::from_be_bytes(buf)) -} - -fn read_i32be(r: &mut &[u8]) -> std::io::Result { - let mut buf = [0u8; 4]; - r.read_exact(&mut buf)?; - Ok(i32::from_be_bytes(buf)) -} - -fn read_i64be(r: &mut &[u8]) -> std::io::Result { - let mut buf = [0u8; 8]; - r.read_exact(&mut buf)?; - Ok(i64::from_be_bytes(buf)) -} - -fn read_f32be(r: &mut &[u8]) -> std::io::Result { - let mut buf = [0u8; 4]; - r.read_exact(&mut buf)?; - Ok(f32::from_be_bytes(buf)) -} - -fn read_f64be(r: &mut &[u8]) -> std::io::Result { - let mut buf = [0u8; 8]; - r.read_exact(&mut buf)?; - Ok(f64::from_be_bytes(buf)) -} - -fn read_utf8_string(r: &mut &[u8]) -> std::io::Result { - let len = VarInt::read(r)? as usize; - if len == 0 { return Ok(String::new()); } - let mut buf = vec![0u8; len]; - r.read_exact(&mut buf)?; - Ok(String::from_utf8(buf) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?) -} - -fn read_varint_array(r: &mut &[u8]) -> std::io::Result> { - let count = VarInt::read(r)?; - let mut v = Vec::with_capacity(count as usize); - for _ in 0..count { v.push(VarInt::read(r)?); } - Ok(v) -} - -fn read_nbt(r: &mut &[u8]) -> std::io::Result> { - if r.is_empty() { return Ok(Vec::new()); } - let tag_type = r[0]; - if tag_type == 0 { *r = &r[1..]; return Ok(Vec::new()); } - let start_len = r.len(); - let mut depth = 1; - let mut i = 1; - if tag_type == 10 { - if r.len() < 3 { return Ok(Vec::new()); } - let name_len = i16::from_be_bytes([r[1], r[2]]) as usize; - i = 3 + name_len; - } - let skip = |pos: &mut usize, n: usize| -> bool { - if *pos + n > r.len() { return false; } - *pos += n; - true - }; - while i < r.len() && depth > 0 { - let tag = r[i]; - i += 1; - match tag { - 0 => { depth -= 1; } - 1 => { if !skip(&mut i, 1) { break; } } - 2 => { if !skip(&mut i, 2) { break; } } - 3 => { if !skip(&mut i, 4) { break; } } - 4 => { if !skip(&mut i, 8) { break; } } - 5 => { if !skip(&mut i, 4) { break; } } - 6 => { if !skip(&mut i, 8) { break; } } - 7 => { - if i + 4 > r.len() { break; } - let len = i32::from_be_bytes([r[i], r[i+1], r[i+2], r[i+3]]) as usize; - if !skip(&mut i, 4 + len) { break; } - } - 8 => { - if i + 2 > r.len() { break; } - let len = i16::from_be_bytes([r[i], r[i+1]]) as usize; - if !skip(&mut i, 2 + len) { break; } - } - 9 => { - if i + 5 > r.len() { break; } - let _inner = r[i]; i += 1; - let len = i32::from_be_bytes([r[i], r[i+1], r[i+2], r[i+3]]) as usize; - i += 4; - depth += len; - } - 10 => { - if i + 2 > r.len() { break; } - let name_len = i16::from_be_bytes([r[i], r[i+1]]) as usize; - if !skip(&mut i, 2 + name_len) { break; } - depth += 1; - } - 11 => { - if i + 4 > r.len() { break; } - let len = i32::from_be_bytes([r[i], r[i+1], r[i+2], r[i+3]]) as usize; - if !skip(&mut i, 4 + len * 4) { break; } - } - 12 => { - if i + 4 > r.len() { break; } - let len = i32::from_be_bytes([r[i], r[i+1], r[i+2], r[i+3]]) as usize; - if !skip(&mut i, 4 + len * 8) { break; } - } - _ => { break; } - } - } - let consumed = start_len.saturating_sub(r.len() - i).min(start_len); - let result = r[..consumed].to_vec(); - *r = &r[consumed..]; - Ok(result) -} - -fn read_java_slot(r: &mut &[u8]) -> std::io::Result { - let present = read_u8be(r)? != 0; - if !present { - Ok(JavaSlot::empty()) - } else { - let item_id = VarInt::read(r)?; - let count = read_u8be(r)?; - let components_len = VarInt::read(r)? as usize; - let mut components = vec![0u8; components_len]; - r.read_exact(&mut components)?; - Ok(JavaSlot { present, item_id, count, components }) - } -} - -fn read_java_metadata(r: &mut &[u8]) -> std::io::Result<()> { - loop { - if r.is_empty() { break; } - let item = r[0]; - if item == 0xFF { *r = &r[1..]; break; } - *r = &r[1..]; - let type_id = item & 0x1F; - match type_id { - 0 => { let _ = read_u8be(r)?; } - 1 => { let _ = VarInt::read(r)?; } - 2 => { let _ = read_f32be(r)?; } - 3 => { let _ = read_utf8_string(r)?; } - 4 => { let _ = read_utf8_string(r)?; } - 5 => { let _ = read_nbt(r)?; } - 6 => { let _ = VarInt::read(r)?; } - 7 => { let _ = read_f32be(r)?; let _ = read_f32be(r)?; } - 8 => { let _ = VarInt::read(r)?; } - 9 => { let _ = VarInt::read(r)?; } - 10 => { let _ = VarInt::read(r)?; } - 11 => { let _ = read_u8be(r)?; } - 12 => { let mut u = [0u8; 16]; r.read_exact(&mut u)?; } - 13 => { let _ = VarInt::read(r)?; } - 14 => { let _ = read_nbt(r)?; } - 15 => { let _ = VarInt::read(r)?; } - 16 => { let _ = read_i64be(r)?; } - 17 => { let _ = VarInt::read(r)?; } - _ => { break; } - } - } - Ok(()) -} diff --git a/src-tauri/src/lce_bridge/java_session.rs b/src-tauri/src/lce_bridge/java_session.rs deleted file mode 100644 index 4b6f3f1..0000000 --- a/src-tauri/src/lce_bridge/java_session.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::lce_bridge::java_protocol::*; -use crate::lce_bridge::config::RemoteConfig; -use crate::lce_bridge::msa_auth; -use crate::lce_bridge::util::VarInt; -use tokio::sync::mpsc; -use tokio::net::TcpStream; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use tokio::sync::Mutex; -use aes::Aes128; -use cipher::{KeyInit, BlockEncrypt}; - -pub struct EncryptionCtx { - pub key: [u8; 16], - pub enc_iv: [u8; 16], - pub dec_iv: [u8; 16], -} - -fn aes_cfb8_encrypt(key: &[u8; 16], iv: &mut [u8; 16], data: &[u8]) -> Vec { - let cipher = Aes128::new_from_slice(key).unwrap(); - let mut result = data.to_vec(); - for i in 0..result.len() { - let mut block = *iv; - cipher.encrypt_block((&mut block).into()); - let keystream = block[0]; - let ct = result[i] ^ keystream; - result[i] = ct; - iv.copy_within(1.., 0); - iv[15] = ct; - } - result -} - -fn aes_cfb8_decrypt(key: &[u8; 16], iv: &mut [u8; 16], data: &[u8]) -> Vec { - let cipher = Aes128::new_from_slice(key).unwrap(); - let mut result = Vec::with_capacity(data.len()); - for &byte in data { - let mut block = *iv; - cipher.encrypt_block((&mut block).into()); - let keystream = block[0]; - let pt = byte ^ keystream; - result.push(pt); - iv.copy_within(1.., 0); - iv[15] = byte; - } - result -} - -fn rsa_encrypt(public_key_der: &[u8], data: &[u8]) -> std::io::Result> { - use rsa::RsaPublicKey; - use rsa::pkcs8::DecodePublicKey; - use rsa::Pkcs1v15Encrypt; - let pubkey = RsaPublicKey::from_public_key_der(public_key_der) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?; - let mut rng = rand::thread_rng(); - pubkey.encrypt(&mut rng, Pkcs1v15Encrypt, data) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) -} - -pub struct JavaSession { - pub tx: mpsc::Sender, - pub rx: mpsc::Receiver, -} - -impl JavaSession { - pub async fn connect(config: &RemoteConfig) -> std::io::Result { - let stream = tokio::time::timeout( - std::time::Duration::from_secs(5), - TcpStream::connect((config.host.as_str(), config.port)), - ).await - .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "connect timeout"))? - .map_err(|e| e)?; - let (reader, writer) = stream.into_split(); - let (packet_tx, packet_rx) = mpsc::channel::(256); - let (write_tx, write_rx) = mpsc::channel::(256); - - let enc_ctx: Arc>> = Arc::new(Mutex::new(None)); - - if config.auth_type == "online" { - let profile = msa_auth::authenticate().await - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - eprintln!("[LCEBridge] Authenticated as {} ({})", profile.username, profile.uuid); - } - - let enc_w = enc_ctx.clone(); - let write_tx_clone = write_tx.clone(); - let comp_state: Arc> = Arc::new(Mutex::new(-1i32)); - tokio::spawn(write_loop(writer, write_rx, enc_w, comp_state.clone())); - - tokio::spawn(read_loop(reader, packet_tx, write_tx_clone, enc_ctx, comp_state, config.clone())); - - Ok(Self { - tx: write_tx, - rx: packet_rx, - }) - } - - pub async fn send(&mut self, packet: JavaPacket) -> std::io::Result<()> { - self.tx.send(packet).await - .map_err(|_| std::io::Error::new(std::io::ErrorKind::ConnectionReset, "send failed")) - } - - pub async fn recv(&mut self) -> Option { - self.rx.recv().await - } -} - -fn encode_frame(packet: &JavaPacket, compression: i32) -> Option> { - let mut payload = Vec::new(); - packet.encode(&mut payload).ok()?; - let packet_id = packet.id(); - let mut frame = VarInt::write(packet_id); - frame.extend_from_slice(&payload); - if compression >= 0 { - let raw_data_len = frame.len() as i32; - let mut compressed_frame = Vec::new(); - if raw_data_len > compression { - use flate2::write::ZlibEncoder; - use flate2::Compression; - use std::io::Write; - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(&frame).ok()?; - let compressed = encoder.finish().ok()?; - VarInt::write_to(raw_data_len, &mut compressed_frame); - compressed_frame.extend_from_slice(&compressed); - } else { - VarInt::write_to(0, &mut compressed_frame); - compressed_frame.extend_from_slice(&frame); - } - frame = compressed_frame; - } - let mut len_prefix = VarInt::write(frame.len() as i32); - len_prefix.extend_from_slice(&frame); - Some(len_prefix) -} - -async fn read_loop( - mut reader: tokio::net::tcp::OwnedReadHalf, - tx: mpsc::Sender, - write_tx: mpsc::Sender, - enc_ctx: Arc>>, - comp_state: Arc>, - config: RemoteConfig, -) { - let mut state: u8 = 1; - let mut compression = -1i32; - let sent_finish_config = Arc::new(AtomicBool::new(false)); - loop { - let mut raw = Vec::new(); - let mut dec = Vec::new(); - loop { - let b = match read_raw_bytes(&mut reader, 1).await { - Ok(b) => b[0], - Err(e) => { - eprintln!("[LCEBridge] Java read loop error reading byte: {}", e); - break; - } - }; - raw.push(b); - let db = { - let mut ctx = enc_ctx.lock().await; - if let Some(ref mut ec) = *ctx { - let result = aes_cfb8_decrypt(&ec.key, &mut ec.dec_iv, &[b]); - result[0] - } else { - b - } - }; - dec.push(db); - if dec.len() == 1 { - eprintln!("[LCEBridge] Java read loop: first byte 0x{:02x} decrypted 0x{:02x}", b, db); - } - if db & 0x80 == 0 || dec.len() >= 5 { - break; - } - } - eprintln!("[LCEBridge] Java read: VarInt raw={:02x?} dec={:02x?}", raw, dec); - if raw.is_empty() { break; } - - let len = match VarInt::read(&mut &dec[..]) { - Ok(l) => { - eprintln!("[LCEBridge] Java read: decoded packet length={}", l); - l - } - Err(e) => { - eprintln!("[LCEBridge] Java read: VarInt failed on first byte 0x{:02x}: {}", raw[0], e); - if let Ok(()) = try_handshake(raw[0], &mut reader, &write_tx, &enc_ctx, &config).await { - continue; - } - eprintln!("[LCEBridge] Java read: try_handshake failed (offline?), breaking read loop"); - break; - } - }; - - let mut packet_data = vec![0u8; len as usize]; - if read_exact_decrypt(&mut reader, &mut packet_data, &enc_ctx).await.is_err() { break; } - if len > 0 { - let show = &packet_data[..packet_data.len().min(20)]; - eprintln!("[LCEBridge] Java read: packet_data len={} hex={:02x?}", len, show); - } - - let decompressed = if compression >= 0 { - let mut data = &packet_data[..]; - let uncompressed_len = match VarInt::read(&mut data) { - Ok(l) => l, - Err(_) => break, - }; - if uncompressed_len > 0 { - use flate2::read::ZlibDecoder; - use std::io::Read; - let mut decoder = ZlibDecoder::new(data); - let mut buf = Vec::with_capacity(uncompressed_len as usize); - if decoder.read_to_end(&mut buf).is_err() { break; } - buf - } else { - data.to_vec() - } - } else { - packet_data - }; - - let mut data = &decompressed[..]; - if data.is_empty() { continue; } - let packet_id = match VarInt::read(&mut data) { - Ok(id) => id, - Err(_) => break, - }; - - let proto_state = match state { - 0 => JavaProtocolState::Handshaking, - 1 => JavaProtocolState::Login, - 2 => JavaProtocolState::Config, - 3 => JavaProtocolState::Play, - _ => JavaProtocolState::Play, - }; - - let packet = match JavaPacket::decode(packet_id, data, &proto_state) { - Ok(p) => p, - Err(e) => { - eprintln!("[LCEBridge] Java read: decode failed for id=0x{:02x} state={:?}: {}", packet_id, proto_state, e); - continue; - } - }; - - match &packet { - JavaPacket::SetCompression(p) => { - eprintln!("[LCEBridge] Java read: SetCompression threshold={}", p.threshold); - compression = p.threshold; - *comp_state.lock().await = p.threshold; - let _ = tx.send(packet).await; - continue; - } - JavaPacket::LoginSuccess(p) => { - eprintln!("[LCEBridge] Java read: LoginSuccess user={}", p.username); - let _ = write_tx.send(JavaPacket::LoginAcknowledged).await; - let _ = tx.send(packet).await; - state = 2; - continue; - } - JavaPacket::FinishConfig => { - eprintln!("[LCEBridge] Java read: FinishConfig from server, sending ours"); - let _ = write_tx.send(JavaPacket::FinishConfig).await; - let _ = tx.send(packet).await; - state = 3; - continue; - } - JavaPacket::Unknown { id: 0x0E, .. } if state == 2 && config.protocol_version >= 766 => { - eprintln!("[LCEBridge] Java read: SelectKnownPacks (0x0E), sending empty response"); - let _ = write_tx.send(JavaPacket::Unknown { id: 0x07, data: vec![0] }).await; - continue; - } - JavaPacket::Unknown { id: 0x07, .. } if state == 2 && config.protocol_version < 766 => { - eprintln!("[LCEBridge] Java read: SelectKnownPacks (0x07), sending empty response"); - let _ = write_tx.send(JavaPacket::Unknown { id: 0x08, data: vec![0] }).await; - continue; - } - JavaPacket::EncryptionRequest(p) => { - handle_encryption_request(p, &write_tx, &enc_ctx).await; - continue; - } - JavaPacket::Unknown { id: 0x09, data } if state == 2 => { - eprintln!("[LCEBridge] Java read: ResourcePackPush, auto-accepting"); - let pack_id = if data.len() >= 16 { - data[..16].to_vec() - } else { - data.clone() - }; - let sid: i32 = if config.protocol_version >= 766 { 0x06 } else { 0x0C }; - for status in &[3, 4, 0] { - let mut resp = pack_id.clone(); - VarInt::write_to(*status, &mut resp); - let _ = write_tx.send(JavaPacket::Unknown { id: sid, data: resp }).await; - } - continue; - } - JavaPacket::Unknown { id: 0x04, data } if state == 2 && data.len() == 8 => { - eprintln!("[LCEBridge] Java read: KeepAlive (Config 0x04), echoing"); - let _ = write_tx.send(JavaPacket::Unknown { id: 0x04, data: data.clone() }).await; - continue; - } - JavaPacket::Unknown { id: 0x04, data } if state == 2 && data.len() == 4 => { - eprintln!("[LCEBridge] Java read: Ping (Config 0x04), sending Pong"); - let _ = write_tx.send(JavaPacket::Unknown { id: 0x05, data: data.clone() }).await; - continue; - } - _ => {} - } - if state == 2 && !sent_finish_config.swap(true, Ordering::Relaxed) { - eprintln!("[LCEBridge] Java read: first server config, waiting for FinishConfig from server"); - } - eprintln!("[LCEBridge] Java read: forwarding packet id={}", packet.id()); - if tx.send(packet).await.is_err() { break; } - } -} - -async fn read_raw_bytes(reader: &mut tokio::net::tcp::OwnedReadHalf, n: usize) -> std::io::Result> { - let mut buf = vec![0u8; n]; - reader.read_exact(&mut buf).await?; - Ok(buf) -} - -async fn read_exact_decrypt(reader: &mut tokio::net::tcp::OwnedReadHalf, buf: &mut [u8], enc_ctx: &Arc>>) -> std::io::Result<()> { - let mut offset = 0; - while offset < buf.len() { - let n = reader.read(&mut buf[offset..]).await?; - if n == 0 { return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")); } - offset += n; - } - let mut ctx = enc_ctx.lock().await; - if let Some(ref mut ec) = *ctx { - let decrypted = aes_cfb8_decrypt(&ec.key, &mut ec.dec_iv, buf); - buf.copy_from_slice(&decrypted); - } - Ok(()) -} - -async fn try_handshake( - first_byte: u8, - reader: &mut tokio::net::tcp::OwnedReadHalf, - write_tx: &mpsc::Sender, - enc_ctx: &Arc>>, - config: &RemoteConfig, -) -> std::io::Result<()> { - if config.auth_type != "online" { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "not online")); - } - let mut more = [0u8; 2]; - reader.read_exact(&mut more).await?; - let mut full = vec![first_byte]; - full.extend_from_slice(&more); - let len = VarInt::read(&mut &full[..]).map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "bad varint"))?; - let mut packet_data = vec![0u8; len as usize]; - reader.read_exact(&mut packet_data).await?; - let mut rest = &packet_data[..]; - let _sid = read_utf8_string(&mut rest)?; - let key_len = VarInt::read(&mut rest)? as usize; - let mut public_key = vec![0u8; key_len]; - std::io::Read::read_exact(&mut rest, &mut public_key)?; - let token_len = VarInt::read(&mut rest)? as usize; - let mut verify_token = vec![0u8; token_len]; - std::io::Read::read_exact(&mut rest, &mut verify_token)?; - - let shared_secret: [u8; 16] = rand::random(); - let encrypted_secret = rsa_encrypt(&public_key, &shared_secret)?; - let encrypted_token = { - let mut iv = shared_secret; - aes_cfb8_encrypt(&shared_secret, &mut iv, &verify_token) - }; - - let response = JavaPacket::EncryptionResponse(EncryptionResponsePacket { - shared_secret: encrypted_secret, - verify_token: encrypted_token, - }); - let _ = write_tx.send(response).await; - - *enc_ctx.lock().await = Some(EncryptionCtx { - key: shared_secret, - enc_iv: shared_secret, - dec_iv: shared_secret, - }); - Ok(()) -} - -async fn handle_encryption_request( - p: &EncryptionRequestPacket, - write_tx: &mpsc::Sender, - enc_ctx: &Arc>>, -) { - let shared_secret: [u8; 16] = rand::random(); - let encrypted_secret = match rsa_encrypt(&p.public_key, &shared_secret) { - Ok(s) => s, - Err(_) => return, - }; - let encrypted_token = { - let mut iv = shared_secret; - aes_cfb8_encrypt(&shared_secret, &mut iv, &p.verify_token) - }; - let response = JavaPacket::EncryptionResponse(EncryptionResponsePacket { - shared_secret: encrypted_secret, - verify_token: encrypted_token, - }); - let _ = write_tx.send(response).await; - *enc_ctx.lock().await = Some(EncryptionCtx { - key: shared_secret, - enc_iv: shared_secret, - dec_iv: shared_secret, - }); -} - -async fn write_loop( - mut writer: tokio::net::tcp::OwnedWriteHalf, - mut rx: mpsc::Receiver, - enc_ctx: Arc>>, - comp_state: Arc>, -) { - while let Some(packet) = rx.recv().await { - let threshold = *comp_state.lock().await; - if let Some(mut frame) = encode_frame(&packet, threshold) { - let needs_encryption = !matches!(packet, JavaPacket::EncryptionResponse(_)); - if needs_encryption { - let mut ctx = enc_ctx.lock().await; - if let Some(ref mut ec) = *ctx { - let encrypted = aes_cfb8_encrypt(&ec.key, &mut ec.enc_iv, &frame); - frame = encrypted; - } - } - let hex: String = frame.iter().map(|b| format!("{:02x}", b)).collect::>().join(" "); - eprintln!("[LCEBridge] Java write: sending len={} hex=[{}]", frame.len(), hex); - if writer.write_all(&frame).await.is_err() { break; } - } - } - let _ = writer.shutdown().await; -} - -fn read_utf8_string(r: &mut &[u8]) -> std::io::Result { - let len = VarInt::read(r)? as usize; - if len == 0 { return Ok(String::new()); } - let mut buf = vec![0u8; len]; - std::io::Read::read_exact(r, &mut buf)?; - Ok(String::from_utf8(buf) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?) -} diff --git a/src-tauri/src/lce_bridge/lce_codec.rs b/src-tauri/src/lce_bridge/lce_codec.rs deleted file mode 100644 index 310b88d..0000000 --- a/src-tauri/src/lce_bridge/lce_codec.rs +++ /dev/null @@ -1,600 +0,0 @@ -use crate::lce_bridge::lce_packets::*; -use crate::lce_bridge::util::*; -use crate::lce_bridge::bridge::LceBridgeSession; -use crate::lce_bridge::config::BridgeConfig; -use std::sync::Arc; -use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::Mutex; - -pub fn decode_lce_packet(id: u8, data: &[u8]) -> std::io::Result { - let mut r = LceReader::new(data); - Ok(match id { - 0 => LcePacket::KeepAlive(KeepAlivePacket { - keep_alive_id: r.read_i32()?, - }), - 1 => LcePacket::Login(LoginPacket { - protocol_version: r.read_i32()?, - username: r.read_string_utf16()?, - map_seed: r.read_u64()?, - game_type: r.read_i32()?, - world_name: r.read_string_utf16()?, - dimension: r.read_i32()?, - difficulty: r.read_i32()?, - max_players: r.read_u8()?, - world_width: r.read_i32()?, - world_length: r.read_i32()?, - }), - 2 => { - let net_version = r.read_i16()?; - let player_name = r.read_string_utf16()?; - let mut offline_xuid = r.read_u8()? as i64; - r.read_i32()?; - let player_count = r.read_u8()?; - let mut online_xuid = 0i64; - for i in 0..player_count { - let off = r.read_i64()?; - let on = r.read_i64()?; - if i == 0 { - offline_xuid = off; - online_xuid = on; - } - } - for _ in 0..14 { r.read_u8()?; } - r.read_i32()?; - r.read_u8()?; - r.read_i32()?; - LcePacket::PreLogin(PreLoginPacket { net_version, player_name, offline_xuid, online_xuid }) - } - 3 => LcePacket::Chat(ChatPacket { - message_type: r.read_u8()?, - string_args: { - let count = r.read_u16()? as usize; - let mut v = Vec::with_capacity(count); - for _ in 0..count { v.push(r.read_string_utf16()?); } - v - }, - int_args: { - let count = r.read_u16()? as usize; - let mut v = Vec::with_capacity(count); - for _ in 0..count { v.push(r.read_i32()?); } - v - }, - }), - 7 => LcePacket::Interact(InteractPacket { - source: r.read_u8()?, - target: r.read_i32()?, - action: r.read_u8()?, - }), - 9 => LcePacket::RespawnRequest(RespawnRequestPacket), - 10 | 11 | 12 | 13 => LcePacket::MovePlayer(MovePlayerPacket { - id, - x: r.read_f64()?, - y: r.read_f64()?, - y_view: r.read_f64()?, - z: r.read_f64()?, - yaw: r.read_f32()?, - pitch: r.read_f32()?, - flags: if id == 13 { r.read_u8()? } else { 0 }, - }), - 14 => LcePacket::PlayerAction(PlayerActionPacket { - action: r.read_u8()?, - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - face: r.read_u8()?, - }), - 15 => LcePacket::UseItem(UseItemPacket { - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - face: r.read_u8()?, - item: read_lce_item(&mut r)?, - click_x: r.read_f32()?, - click_y: r.read_f32()?, - click_z: r.read_f32()?, - }), - 16 => LcePacket::SetCarriedItem(SetCarriedItemPacket { - slot: r.read_u8()?, - }), - 18 => LcePacket::Animate(AnimatePacket { - entity_id: r.read_i32()?, - action: r.read_u8()?, - }), - 19 => LcePacket::PlayerCommand(PlayerCommandPacket { - entity_id: r.read_i32()?, - action: r.read_u8()?, - data: r.read_i32()?, - }), - 20 => LcePacket::AddPlayer(AddPlayerPacket { - entity_id: r.read_i32()?, - name: r.read_string_utf16()?, - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - yaw: r.read_u8()?, - pitch: r.read_u8()?, - head_yaw: r.read_u8()?, - carried_item: r.read_u16()?, - offline_xuid: r.read_i64()?, - online_xuid: r.read_i64()?, - player_index: r.read_i32()?, - skin_id: r.read_string_utf16()?, - cape_id: r.read_string_utf16()?, - game_privileges: r.read_u32()?, - metadata: read_entity_metadata(&mut r)?, - }), - 23 => LcePacket::AddEntity(AddEntityPacket { - entity_id: r.read_i32()?, - entity_type: r.read_u8()?, - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - yaw: r.read_u8()?, - pitch: r.read_u8()?, - data: r.read_i32()?, - motion_x: r.read_i16()?, - motion_y: r.read_i16()?, - motion_z: r.read_i16()?, - }), - 24 => LcePacket::AddMob(AddMobPacket { - entity_id: r.read_i32()?, - entity_type: r.read_u8()?, - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - yaw: r.read_u8()?, - pitch: r.read_u8()?, - head_yaw: r.read_u8()?, - motion_x: r.read_i16()?, - motion_y: r.read_i16()?, - motion_z: r.read_i16()?, - metadata: read_entity_metadata(&mut r)?, - }), - 28 => LcePacket::SetEntityMotion(SetEntityMotionPacket { - entity_id: r.read_i32()?, - xa: r.read_i16()?, - ya: r.read_i16()?, - za: r.read_i16()?, - }), - 29 => { - let count = r.read_u16()? as usize; - let mut entity_ids = Vec::with_capacity(count); - for _ in 0..count { entity_ids.push(r.read_i32()?); } - LcePacket::RemoveEntities(RemoveEntitiesPacket { entity_ids }) - } - 34 => LcePacket::TeleportEntity(TeleportEntityPacket { - entity_id: r.read_i32()?, - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - yaw: r.read_u8()?, - pitch: r.read_u8()?, - }), - 35 => LcePacket::RotateHead(RotateHeadPacket { - entity_id: r.read_i32()?, - y_head_rot: r.read_u8()?, - }), - 38 => LcePacket::EntityEvent(EntityEventPacket { - entity_id: r.read_i32()?, - event_id: r.read_u8()?, - }), - 40 => { - let entity_id = r.read_i32()?; - let values = read_entity_metadata(&mut r)?; - LcePacket::SetEntityData(SetEntityDataPacket { entity_id, values }) - } - 50 => LcePacket::ChunkVisibility(ChunkVisibilityPacket { - chunk_x: r.read_i32()?, - chunk_z: r.read_i32()?, - visible: r.read_bool()?, - }), - 51 => LcePacket::BlockRegionUpdate(BlockRegionUpdatePacket { - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - xs: r.read_u8()?, - ys: r.read_u8()?, - zs: r.read_u8()?, - level_idx: r.read_u8()?, - is_full_chunk: r.read_bool()?, - compressed_data: { - let len = r.read_u32()? as usize; - r.read_bytes(len)? - }, - }), - 53 => LcePacket::TileUpdate(TileUpdatePacket { - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - block: r.read_u16()?, - data: r.read_u16()?, - level_idx: r.read_u8()?, - }), - 70 => LcePacket::GameEvent(GameEventPacket { - reason: r.read_u8()?, - param: r.read_f32()?, - }), - 100 => LcePacket::ContainerOpen(ContainerOpenPacket { - container_id: r.read_u8()?, - container_type: r.read_u8()?, - size: r.read_u8()?, - custom_name: r.read_string_utf16()?, - title: r.read_string_utf16()?, - entity_id: r.read_i32()?, - }), - 101 => LcePacket::ContainerClose(ContainerClosePacket { - container_id: r.read_u8()?, - }), - 102 => LcePacket::ContainerClick(ContainerClickPacket { - container_id: r.read_u8()?, - slot_num: r.read_i16()?, - button_num: r.read_u8()?, - uid: r.read_i16()?, - click_type: r.read_u8()?, - item: read_lce_item(&mut r)?, - }), - 103 => LcePacket::ContainerSetSlot(ContainerSetSlotPacket { - container_id: r.read_u8()?, - slot: r.read_i16()?, - item: read_lce_item(&mut r)?, - }), - 104 => LcePacket::ContainerSetContent(ContainerSetContentPacket { - container_id: r.read_u8()?, - items: { - let count = r.read_u16()? as usize; - let mut v = Vec::with_capacity(count); - for _ in 0..count { v.push(read_lce_item(&mut r)?); } - v - }, - }), - 105 => LcePacket::ContainerSetData(ContainerSetDataPacket { - container_id: r.read_u8()?, - id: r.read_i16()?, - value: r.read_i16()?, - }), - 106 => LcePacket::ContainerAck(ContainerAckPacket { - container_id: r.read_u8()?, - uid: r.read_i16()?, - accepted: r.read_bool()?, - }), - 108 => LcePacket::ContainerButtonClick(ContainerButtonClickPacket { - container_id: r.read_u8()?, - button_id: r.read_u8()?, - }), - 150 => LcePacket::CraftItem(CraftItemPacket { - uid: r.read_i16()?, - recipe: r.read_u8()?, - }), - 152 => LcePacket::DebugOptions(DebugOptionsPacket { - options_mask: r.read_u32()?, - }), - 155 => LcePacket::ChunkVisibilityArea(ChunkVisibilityAreaPacket { - min_cx: r.read_i32()?, - max_cx: r.read_i32()?, - min_cz: r.read_i32()?, - max_cz: r.read_i32()?, - }), - 202 => LcePacket::PlayerAbilities(PlayerAbilitiesPacket { - flags: r.read_u8()?, - fly_speed: r.read_f32()?, - walk_speed: r.read_f32()?, - }), - 205 => LcePacket::ClientCommand(ClientCommandPacket { - action: r.read_u8()?, - }), - 255 => LcePacket::Disconnect(DisconnectPacket { - reason: r.read_u8()?, - }), - 4 => LcePacket::SetTime(SetTimePacket { - game_time: r.read_i64()?, - day_time: r.read_i64()?, - }), - 6 => LcePacket::SetSpawnPosition(SetSpawnPositionPacket { - x: r.read_i32()?, - y: r.read_i32()?, - z: r.read_i32()?, - }), - 8 => LcePacket::SetHealth(SetHealthPacket { - health: r.read_f32()?, - food: r.read_i32()?, - saturation: r.read_f32()?, - damage_source: r.read_u8()?, - }), - _ => LcePacket::Raw { id, data: data.to_vec() }, - }) -} - -pub fn encode_lce_packet(packet: &LcePacket) -> Vec { - let id = packet.id(); - let mut w = LceWriter::new(); - match packet { - LcePacket::KeepAlive(p) => { w.write_i32(p.keep_alive_id); } - LcePacket::Login(p) => { - w.write_i32(p.protocol_version); - w.write_utf16(&p.username); - w.write_u64(p.map_seed); - w.write_i32(p.game_type); - w.write_utf16(&p.world_name); - w.write_i32(p.dimension); - w.write_i32(p.difficulty); - w.write_u8(p.max_players); - w.write_i32(p.world_width); - w.write_i32(p.world_length); - } - LcePacket::PreLogin(p) => { - w.write_i16(p.net_version); - w.write_utf16(&p.player_name); - w.write_i64(p.offline_xuid); - w.write_i64(p.online_xuid); - } - LcePacket::Chat(p) => { - w.write_u8(p.message_type); - w.write_u16(p.string_args.len() as u16); - for s in &p.string_args { w.write_utf16(s); } - w.write_u16(p.int_args.len() as u16); - for v in &p.int_args { w.write_i32(*v); } - } - LcePacket::Disconnect(p) => { w.write_u8(p.reason); } - LcePacket::Animate(p) => { w.write_i32(p.entity_id); w.write_u8(p.action); } - LcePacket::MovePlayer(p) => { - w.write_f64(p.x); w.write_f64(p.y); w.write_f64(p.y_view); w.write_f64(p.z); - w.write_f32(p.yaw); w.write_f32(p.pitch); - if p.id == 13 { w.write_u8(p.flags); } - } - LcePacket::PlayerAction(p) => { - w.write_u8(p.action); w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); w.write_u8(p.face); - } - LcePacket::Interact(p) => { w.write_u8(p.source); w.write_i32(p.target); w.write_u8(p.action); } - LcePacket::UseItem(p) => { - w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); w.write_u8(p.face); - write_lce_item(&mut w, &p.item); - w.write_f32(p.click_x); w.write_f32(p.click_y); w.write_f32(p.click_z); - } - LcePacket::PlayerCommand(p) => { w.write_i32(p.entity_id); w.write_u8(p.action); w.write_i32(p.data); } - LcePacket::SetCarriedItem(p) => { w.write_u8(p.slot); } - LcePacket::PlayerAbilities(p) => { w.write_u8(p.flags); w.write_f32(p.fly_speed); w.write_f32(p.walk_speed); } - LcePacket::DebugOptions(p) => { w.write_u32(p.options_mask); } - LcePacket::ClientCommand(p) => { w.write_u8(p.action); } - LcePacket::RespawnRequest(_) => {} - LcePacket::ContainerClose(p) => { w.write_u8(p.container_id); } - LcePacket::ContainerClick(p) => { - w.write_u8(p.container_id); w.write_i16(p.slot_num); w.write_u8(p.button_num); - w.write_i16(p.uid); w.write_u8(p.click_type); - write_lce_item(&mut w, &p.item); - } - LcePacket::ContainerButtonClick(p) => { w.write_u8(p.container_id); w.write_u8(p.button_id); } - LcePacket::CraftItem(p) => { w.write_i16(p.uid); w.write_u8(p.recipe); } - LcePacket::SetTime(p) => { w.write_i64(p.game_time); w.write_i64(p.day_time); } - LcePacket::SetSpawnPosition(p) => { w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); } - LcePacket::SetHealth(p) => { w.write_f32(p.health); w.write_i32(p.food); w.write_f32(p.saturation); w.write_u8(p.damage_source); } - LcePacket::GameEvent(p) => { w.write_u8(p.reason); w.write_f32(p.param); } - LcePacket::EntityEvent(p) => { w.write_i32(p.entity_id); w.write_u8(p.event_id); } - LcePacket::ContainerOpen(p) => { - w.write_u8(p.container_id); w.write_u8(p.container_type); w.write_u8(p.size); - w.write_utf16(&p.custom_name); w.write_utf16(&p.title); w.write_i32(p.entity_id); - } - LcePacket::ContainerSetSlot(p) => { w.write_u8(p.container_id); w.write_i16(p.slot); write_lce_item(&mut w, &p.item); } - LcePacket::ContainerSetContent(p) => { - w.write_u8(p.container_id); - w.write_u16(p.items.len() as u16); - for item in &p.items { write_lce_item(&mut w, item); } - } - LcePacket::ContainerSetData(p) => { w.write_u8(p.container_id); w.write_i16(p.id); w.write_i16(p.value); } - LcePacket::ContainerAck(p) => { w.write_u8(p.container_id); w.write_i16(p.uid); w.write_bool(p.accepted); } - LcePacket::ChunkVisibility(p) => { w.write_i32(p.chunk_x); w.write_i32(p.chunk_z); w.write_bool(p.visible); } - LcePacket::ChunkVisibilityArea(p) => { w.write_i32(p.min_cx); w.write_i32(p.max_cx); w.write_i32(p.min_cz); w.write_i32(p.max_cz); } - LcePacket::BlockRegionUpdate(p) => { - w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); - w.write_u8(p.xs); w.write_u8(p.ys); w.write_u8(p.zs); - w.write_u8(p.level_idx); w.write_bool(p.is_full_chunk); - w.write_u32(p.compressed_data.len() as u32); - w.write_bytes(&p.compressed_data); - } - LcePacket::TileUpdate(p) => { w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); w.write_u16(p.block); w.write_u16(p.data); w.write_u8(p.level_idx); } - LcePacket::AddPlayer(p) => { - w.write_i32(p.entity_id); w.write_utf16(&p.name); - w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); - w.write_u8(p.yaw); w.write_u8(p.pitch); w.write_u8(p.head_yaw); - w.write_u16(p.carried_item); - w.write_i64(p.offline_xuid); w.write_i64(p.online_xuid); - w.write_i32(p.player_index); - w.write_utf16(&p.skin_id); w.write_utf16(&p.cape_id); - w.write_u32(p.game_privileges); - write_entity_metadata(&mut w, &p.metadata); - } - LcePacket::AddEntity(p) => { - w.write_i32(p.entity_id); w.write_u8(p.entity_type); - w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); - w.write_u8(p.yaw); w.write_u8(p.pitch); w.write_i32(p.data); - w.write_i16(p.motion_x); w.write_i16(p.motion_y); w.write_i16(p.motion_z); - } - LcePacket::AddMob(p) => { - w.write_i32(p.entity_id); w.write_u8(p.entity_type); - w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); - w.write_u8(p.yaw); w.write_u8(p.pitch); w.write_u8(p.head_yaw); - w.write_i16(p.motion_x); w.write_i16(p.motion_y); w.write_i16(p.motion_z); - write_entity_metadata(&mut w, &p.metadata); - } - LcePacket::TeleportEntity(p) => { w.write_i32(p.entity_id); w.write_i32(p.x); w.write_i32(p.y); w.write_i32(p.z); w.write_u8(p.yaw); w.write_u8(p.pitch); } - LcePacket::RotateHead(p) => { w.write_i32(p.entity_id); w.write_u8(p.y_head_rot); } - LcePacket::SetEntityMotion(p) => { w.write_i32(p.entity_id); w.write_i16(p.xa); w.write_i16(p.ya); w.write_i16(p.za); } - LcePacket::RemoveEntities(p) => { - w.write_u16(p.entity_ids.len() as u16); - for id in &p.entity_ids { w.write_i32(*id); } - } - LcePacket::SetEntityData(p) => { - w.write_i32(p.entity_id); - write_entity_metadata(&mut w, &p.values); - } - LcePacket::Raw { data, .. } => { w.write_bytes(data); } - } - let payload = w.into_bytes(); - let mut frame = Vec::with_capacity(4 + 1 + payload.len()); - frame.extend_from_slice(&(payload.len() as u32 + 1).to_be_bytes()); - frame.push(id); - frame.extend_from_slice(&payload); - frame -} - -fn read_lce_item(r: &mut LceReader) -> std::io::Result { - let id = r.read_i16()?; - let count = r.read_u8()?; - let damage = r.read_i16()?; - let _nbt_size = r.read_i16()?; - if _nbt_size > 0 { r.skip(_nbt_size as usize)?; } - Ok(LceItemStack { id, count, damage }) -} - -fn write_lce_item(w: &mut LceWriter, item: &LceItemStack) { - w.write_i16(item.id); - w.write_u8(item.count); - w.write_i16(item.damage); - w.write_i16(0); -} - -fn read_entity_metadata(r: &mut LceReader) -> std::io::Result> { - let mut values = Vec::new(); - loop { - let id = r.read_u8()?; - if id == 0x7F { break; } - let data_type = r.read_u8()?; - let value = match data_type { - 0 => EntityDataValueType::Byte(r.read_u8()?), - 1 => EntityDataValueType::Short(r.read_i16()?), - 2 => EntityDataValueType::Int(r.read_i32()?), - 3 => EntityDataValueType::Float(r.read_f32()?), - 4 => EntityDataValueType::String(r.read_string_utf16()?), - 5 => EntityDataValueType::ItemStack(read_lce_item(r)?), - _ => { r.skip(1)?; EntityDataValueType::Byte(0) } - }; - values.push(EntityDataValue { id, data_type, value }); - } - Ok(values) -} - -fn write_entity_metadata(w: &mut LceWriter, values: &[EntityDataValue]) { - for v in values { - w.write_u8(v.id); - w.write_u8(v.data_type); - match &v.value { - EntityDataValueType::Byte(b) => w.write_u8(*b), - EntityDataValueType::Short(s) => w.write_i16(*s), - EntityDataValueType::Int(i) => w.write_i32(*i), - EntityDataValueType::Float(f) => w.write_f32(*f), - EntityDataValueType::String(s) => w.write_utf16(s), - EntityDataValueType::ItemStack(item) => write_lce_item(w, item), - } - } - w.write_u8(0x7F); -} - -pub async fn run_lce_server(config: BridgeConfig) -> std::io::Result<()> { - let addr = format!("{}:{}", config.lce.listen_address, config.lce.port); - let listener = TcpListener::bind(&addr).await?; - eprintln!("[LCEBridge] Listening on {}", addr); - loop { - let (stream, addr) = listener.accept().await?; - eprintln!("[LCEBridge] Connection from {}", addr); - let config = config.clone(); - tokio::spawn(async move { - if let Err(e) = handle_connection(stream, config).await { - eprintln!("[LCEBridge] Connection error: {}", e); - } - }); - } -} - -async fn handle_connection(stream: TcpStream, config: BridgeConfig) -> std::io::Result<()> { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let (mut reader, mut writer) = stream.into_split(); - let small_id: u8 = 1; - writer.write_all(&small_id.to_be_bytes()).await?; - eprintln!("[LCEBridge] small_id sent"); - let session = Arc::new(Mutex::new(LceBridgeSession::new(config))); - { - let mut s = session.lock().await; - s.on_connected(); - } - let session_clone = session.clone(); - let read_handle = tokio::spawn(async move { - let mut header = [0u8; 4]; - loop { - match reader.read_exact(&mut header).await { - Ok(_) => {} - Err(_) => { - eprintln!("[LCEBridge] LCE read error (connection closed?)"); - break; - } - } - let payload_len = u32::from_be_bytes(header) as usize; - eprintln!("[LCEBridge] received frame len={}", payload_len); - if payload_len > 2_097_152 { - break; - } - let mut payload = vec![0u8; payload_len]; - if reader.read_exact(&mut payload).await.is_err() { - break; - } - let hex: String = payload.iter().map(|b| format!("{:02x}", b)).collect::>().join(" "); - eprintln!("[LCEBridge] frame hex=[{}]", hex); - let mut offset = 0; - while offset < payload_len { - if offset >= payload_len { break; } - let packet_id = payload[offset]; - offset += 1; - let remaining = payload_len - offset; - let packet_data = &payload[offset..offset + remaining]; - match decode_lce_packet(packet_id, packet_data) { - Ok(packet) => { - let mut s = session_clone.lock().await; - s.handle_lce_packet(packet).await; - } - Err(e) => { - eprintln!("[LCEBridge] decode failed for packet_id={} len={}: {}", packet_id, remaining, e); - } - } - offset += remaining; - } - } - }); - let write_clone = session.clone(); - let _write_handle = tokio::spawn(async move { - let mut out_buf: Vec = Vec::with_capacity(4096); - loop { - out_buf.clear(); - let mut s = write_clone.lock().await; - while let Some(packet) = s.lce_tx.pop_front() { - let frame = encode_lce_packet(&packet); - out_buf.extend_from_slice(&frame); - } - drop(s); - if !out_buf.is_empty() { - if writer.write_all(&out_buf).await.is_err() { break; } - } - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - } - }); - let java_clone = session.clone(); - tokio::spawn(async move { - use tokio::sync::mpsc::error::TryRecvError; - loop { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - let packet = { - let mut s = java_clone.lock().await; - let js = match s.java_session.as_mut() { - Some(js) => js, - None => continue, - }; - match js.rx.try_recv() { - Ok(p) => p, - Err(TryRecvError::Empty) => continue, - Err(TryRecvError::Disconnected) => return, - } - }; - let mut s = java_clone.lock().await; - s.handle_java_packet(packet).await; - } - }); - let _ = read_handle.await; - Ok(()) -} diff --git a/src-tauri/src/lce_bridge/lce_packets.rs b/src-tauri/src/lce_bridge/lce_packets.rs deleted file mode 100644 index 04f88c2..0000000 --- a/src-tauri/src/lce_bridge/lce_packets.rs +++ /dev/null @@ -1,478 +0,0 @@ -#[derive(Debug, Clone)] -pub enum LcePacket { - KeepAlive(KeepAlivePacket), - Login(LoginPacket), - PreLogin(PreLoginPacket), - Chat(ChatPacket), - Disconnect(DisconnectPacket), - Animate(AnimatePacket), - MovePlayer(MovePlayerPacket), - PlayerAction(PlayerActionPacket), - Interact(InteractPacket), - UseItem(UseItemPacket), - PlayerCommand(PlayerCommandPacket), - SetCarriedItem(SetCarriedItemPacket), - PlayerAbilities(PlayerAbilitiesPacket), - DebugOptions(DebugOptionsPacket), - ClientCommand(ClientCommandPacket), - RespawnRequest(RespawnRequestPacket), - ContainerClose(ContainerClosePacket), - ContainerClick(ContainerClickPacket), - ContainerButtonClick(ContainerButtonClickPacket), - CraftItem(CraftItemPacket), - SetTime(SetTimePacket), - SetSpawnPosition(SetSpawnPositionPacket), - SetHealth(SetHealthPacket), - GameEvent(GameEventPacket), - EntityEvent(EntityEventPacket), - ContainerOpen(ContainerOpenPacket), - ContainerSetSlot(ContainerSetSlotPacket), - ContainerSetContent(ContainerSetContentPacket), - ContainerSetData(ContainerSetDataPacket), - ContainerAck(ContainerAckPacket), - ChunkVisibility(ChunkVisibilityPacket), - ChunkVisibilityArea(ChunkVisibilityAreaPacket), - BlockRegionUpdate(BlockRegionUpdatePacket), - TileUpdate(TileUpdatePacket), - AddPlayer(AddPlayerPacket), - AddEntity(AddEntityPacket), - AddMob(AddMobPacket), - TeleportEntity(TeleportEntityPacket), - RotateHead(RotateHeadPacket), - SetEntityMotion(SetEntityMotionPacket), - RemoveEntities(RemoveEntitiesPacket), - SetEntityData(SetEntityDataPacket), - Raw { id: u8, data: Vec }, -} - -impl LcePacket { - pub fn id(&self) -> u8 { - match self { - LcePacket::KeepAlive(_) => 0, - LcePacket::Login(_) => 1, - LcePacket::PreLogin(_) => 2, - LcePacket::Chat(_) => 3, - LcePacket::Disconnect(_) => 255, - LcePacket::Animate(_) => 18, - LcePacket::MovePlayer(_) => 10, - LcePacket::PlayerAction(_) => 14, - LcePacket::Interact(_) => 7, - LcePacket::UseItem(_) => 15, - LcePacket::PlayerCommand(_) => 19, - LcePacket::SetCarriedItem(_) => 16, - LcePacket::PlayerAbilities(_) => 202, - LcePacket::DebugOptions(_) => 152, - LcePacket::ClientCommand(_) => 205, - LcePacket::RespawnRequest(_) => 9, - LcePacket::ContainerClose(_) => 101, - LcePacket::ContainerClick(_) => 102, - LcePacket::ContainerButtonClick(_) => 108, - LcePacket::CraftItem(_) => 150, - LcePacket::SetTime(_) => 4, - LcePacket::SetSpawnPosition(_) => 6, - LcePacket::SetHealth(_) => 8, - LcePacket::GameEvent(_) => 70, - LcePacket::EntityEvent(_) => 38, - LcePacket::ContainerOpen(_) => 100, - LcePacket::ContainerSetSlot(_) => 103, - LcePacket::ContainerSetContent(_) => 104, - LcePacket::ContainerSetData(_) => 105, - LcePacket::ContainerAck(_) => 106, - LcePacket::ChunkVisibility(_) => 50, - LcePacket::ChunkVisibilityArea(_) => 155, - LcePacket::BlockRegionUpdate(_) => 51, - LcePacket::TileUpdate(_) => 53, - LcePacket::AddPlayer(_) => 20, - LcePacket::AddEntity(_) => 23, - LcePacket::AddMob(_) => 24, - LcePacket::TeleportEntity(_) => 34, - LcePacket::RotateHead(_) => 35, - LcePacket::SetEntityMotion(_) => 28, - LcePacket::RemoveEntities(_) => 29, - LcePacket::SetEntityData(_) => 40, - LcePacket::Raw { id, .. } => *id, - } - } -} - -#[derive(Debug, Clone)] -pub struct KeepAlivePacket { - pub keep_alive_id: i32, -} - -#[derive(Debug, Clone)] -pub struct LoginPacket { - pub protocol_version: i32, - pub username: String, - pub map_seed: u64, - pub game_type: i32, - pub world_name: String, - pub dimension: i32, - pub difficulty: i32, - pub max_players: u8, - pub world_width: i32, - pub world_length: i32, -} - -#[derive(Debug, Clone)] -pub struct PreLoginPacket { - pub net_version: i16, - pub player_name: String, - pub offline_xuid: i64, - pub online_xuid: i64, -} - -#[derive(Debug, Clone)] -pub struct ChatPacket { - pub message_type: u8, - pub string_args: Vec, - pub int_args: Vec, -} - -impl ChatPacket { - pub fn set_message(msg: &str) -> Self { - Self { - message_type: 1, - string_args: vec![msg.to_string()], - int_args: vec![], - } - } -} - -#[derive(Debug, Clone)] -pub struct DisconnectPacket { - pub reason: u8, -} - -#[derive(Debug, Clone)] -pub struct AnimatePacket { - pub entity_id: i32, - pub action: u8, -} - -#[derive(Debug, Clone)] -pub struct MovePlayerPacket { - pub id: u8, - pub x: f64, - pub y: f64, - pub y_view: f64, - pub z: f64, - pub yaw: f32, - pub pitch: f32, - pub flags: u8, -} - -#[derive(Debug, Clone)] -pub struct PlayerActionPacket { - pub action: u8, - pub x: i32, - pub y: i32, - pub z: i32, - pub face: u8, -} - -#[derive(Debug, Clone)] -pub struct PlayerCommandPacket { - pub entity_id: i32, - pub action: u8, - pub data: i32, -} - -#[derive(Debug, Clone)] -pub struct InteractPacket { - pub source: u8, - pub target: i32, - pub action: u8, -} - -#[derive(Debug, Clone)] -pub struct UseItemPacket { - pub x: i32, - pub y: i32, - pub z: i32, - pub face: u8, - pub item: LceItemStack, - pub click_x: f32, - pub click_y: f32, - pub click_z: f32, -} - -#[derive(Debug, Clone)] -pub struct SetCarriedItemPacket { - pub slot: u8, -} - -#[derive(Debug, Clone)] -pub struct PlayerAbilitiesPacket { - pub flags: u8, - pub fly_speed: f32, - pub walk_speed: f32, -} - -#[derive(Debug, Clone)] -pub struct DebugOptionsPacket { - pub options_mask: u32, -} - -#[derive(Debug, Clone)] -pub struct ClientCommandPacket { - pub action: u8, -} - -#[derive(Debug, Clone)] -pub struct RespawnRequestPacket; - -#[derive(Debug, Clone)] -pub struct ContainerClosePacket { - pub container_id: u8, -} - -#[derive(Debug, Clone)] -pub struct ContainerClickPacket { - pub container_id: u8, - pub slot_num: i16, - pub button_num: u8, - pub uid: i16, - pub click_type: u8, - pub item: LceItemStack, -} - -#[derive(Debug, Clone)] -pub struct ContainerButtonClickPacket { - pub container_id: u8, - pub button_id: u8, -} - -#[derive(Debug, Clone)] -pub struct CraftItemPacket { - pub uid: i16, - pub recipe: u8, -} - -#[derive(Debug, Clone)] -pub struct SetTimePacket { - pub game_time: i64, - pub day_time: i64, -} - -#[derive(Debug, Clone)] -pub struct SetSpawnPositionPacket { - pub x: i32, - pub y: i32, - pub z: i32, -} - -#[derive(Debug, Clone)] -pub struct SetHealthPacket { - pub health: f32, - pub food: i32, - pub saturation: f32, - pub damage_source: u8, -} - -#[derive(Debug, Clone)] -pub struct GameEventPacket { - pub reason: u8, - pub param: f32, -} - -#[derive(Debug, Clone)] -pub struct EntityEventPacket { - pub entity_id: i32, - pub event_id: u8, -} - -#[derive(Debug, Clone)] -pub struct ContainerOpenPacket { - pub container_id: u8, - pub container_type: u8, - pub size: u8, - pub custom_name: String, - pub title: String, - pub entity_id: i32, -} - -#[derive(Debug, Clone)] -pub struct ContainerSetSlotPacket { - pub container_id: u8, - pub slot: i16, - pub item: LceItemStack, -} - -#[derive(Debug, Clone)] -pub struct ContainerSetContentPacket { - pub container_id: u8, - pub items: Vec, -} - -#[derive(Debug, Clone)] -pub struct ContainerSetDataPacket { - pub container_id: u8, - pub id: i16, - pub value: i16, -} - -#[derive(Debug, Clone)] -pub struct ContainerAckPacket { - pub container_id: u8, - pub uid: i16, - pub accepted: bool, -} - -#[derive(Debug, Clone)] -pub struct ChunkVisibilityPacket { - pub chunk_x: i32, - pub chunk_z: i32, - pub visible: bool, -} - -#[derive(Debug, Clone)] -pub struct ChunkVisibilityAreaPacket { - pub min_cx: i32, - pub max_cx: i32, - pub min_cz: i32, - pub max_cz: i32, -} - -#[derive(Debug, Clone)] -pub struct BlockRegionUpdatePacket { - pub x: i32, - pub y: i32, - pub z: i32, - pub xs: u8, - pub ys: u8, - pub zs: u8, - pub level_idx: u8, - pub is_full_chunk: bool, - pub compressed_data: Vec, -} - -#[derive(Debug, Clone)] -pub struct TileUpdatePacket { - pub x: i32, - pub y: i32, - pub z: i32, - pub block: u16, - pub data: u16, - pub level_idx: u8, -} - -#[derive(Debug, Clone)] -pub struct AddPlayerPacket { - pub entity_id: i32, - pub name: String, - pub x: i32, - pub y: i32, - pub z: i32, - pub yaw: u8, - pub pitch: u8, - pub head_yaw: u8, - pub carried_item: u16, - pub offline_xuid: i64, - pub online_xuid: i64, - pub player_index: i32, - pub skin_id: String, - pub cape_id: String, - pub game_privileges: u32, - pub metadata: Vec, -} - -#[derive(Debug, Clone)] -pub struct AddEntityPacket { - pub entity_id: i32, - pub entity_type: u8, - pub x: i32, - pub y: i32, - pub z: i32, - pub yaw: u8, - pub pitch: u8, - pub data: i32, - pub motion_x: i16, - pub motion_y: i16, - pub motion_z: i16, -} - -#[derive(Debug, Clone)] -pub struct AddMobPacket { - pub entity_id: i32, - pub entity_type: u8, - pub x: i32, - pub y: i32, - pub z: i32, - pub yaw: u8, - pub pitch: u8, - pub head_yaw: u8, - pub motion_x: i16, - pub motion_y: i16, - pub motion_z: i16, - pub metadata: Vec, -} - -#[derive(Debug, Clone)] -pub struct TeleportEntityPacket { - pub entity_id: i32, - pub x: i32, - pub y: i32, - pub z: i32, - pub yaw: u8, - pub pitch: u8, -} - -#[derive(Debug, Clone)] -pub struct RotateHeadPacket { - pub entity_id: i32, - pub y_head_rot: u8, -} - -#[derive(Debug, Clone)] -pub struct SetEntityMotionPacket { - pub entity_id: i32, - pub xa: i16, - pub ya: i16, - pub za: i16, -} - -#[derive(Debug, Clone)] -pub struct RemoveEntitiesPacket { - pub entity_ids: Vec, -} - -#[derive(Debug, Clone)] -pub struct SetEntityDataPacket { - pub entity_id: i32, - pub values: Vec, -} - -#[derive(Debug, Clone)] -pub struct EntityDataValue { - pub id: u8, - pub data_type: u8, - pub value: EntityDataValueType, -} - -#[derive(Debug, Clone)] -pub enum EntityDataValueType { - Byte(u8), - Short(i16), - Int(i32), - Float(f32), - String(String), - ItemStack(LceItemStack), -} - -#[derive(Debug, Clone, Default)] -pub struct LceItemStack { - pub id: i16, - pub count: u8, - pub damage: i16, -} - -impl LceItemStack { - pub fn is_empty(&self) -> bool { - self.id <= 0 || self.count == 0 - } - - pub fn empty() -> Self { - Self { id: 0, count: 0, damage: 0 } - } -} diff --git a/src-tauri/src/lce_bridge/mod.rs b/src-tauri/src/lce_bridge/mod.rs deleted file mode 100644 index eab7368..0000000 --- a/src-tauri/src/lce_bridge/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -pub mod config; -pub mod util; -pub mod lce_packets; -pub mod lce_codec; -pub mod java_protocol; -pub mod java_session; -pub mod registry; -pub mod chunk; -pub mod crafting; -pub mod bridge; -pub mod msa_auth; - -use crate::lce_bridge::config::BridgeConfig; -use crate::lce_bridge::lce_codec::run_lce_server; -use std::sync::Arc; -use tokio::sync::Mutex; - -pub struct BridgeHandle { - cancel: tokio::sync::watch::Sender, -} - -static BRIDGE_INSTANCE: std::sync::OnceLock>>> = std::sync::OnceLock::new(); - -fn bridge_instance() -> &'static Arc>> { - BRIDGE_INSTANCE.get_or_init(|| Arc::new(Mutex::new(None))) -} - -pub async fn start_bridge(config: BridgeConfig) -> Result { - let (cancel_tx, cancel_rx) = tokio::sync::watch::channel(false); - let handle = BridgeHandle { cancel: cancel_tx.clone() }; - *bridge_instance().lock().await = Some(handle); - let config_clone = config.clone(); - tokio::spawn(async move { - let mut cancel_rx = cancel_rx; - let server_fut = run_lce_server(config_clone); - tokio::select! { - _ = server_fut => {} - _ = cancel_rx.changed() => {} - } - }); - Ok(format!("LCEBridge started on {}:{}", config.lce.listen_address, config.lce.port)) -} - -pub async fn stop_bridge() -> Result { - let mut guard = bridge_instance().lock().await; - if let Some(handle) = guard.take() { - let _ = handle.cancel.send(true); - Ok("LCEBridge stopped".to_string()) - } else { - Err("LCEBridge is not running".to_string()) - } -} - -pub async fn bridge_status() -> Result { - let guard = bridge_instance().lock().await; - if guard.is_some() { - Ok("running".to_string()) - } else { - Ok("stopped".to_string()) - } -} diff --git a/src-tauri/src/lce_bridge/msa_auth.rs b/src-tauri/src/lce_bridge/msa_auth.rs deleted file mode 100644 index 4f61d1d..0000000 --- a/src-tauri/src/lce_bridge/msa_auth.rs +++ /dev/null @@ -1,384 +0,0 @@ -use base64::Engine; -use p256::ecdsa::{SigningKey, Signature, signature::Signer}; -use p256::elliptic_curve::sec1::ToEncodedPoint; -use p256::SecretKey; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::sync::OnceLock; -use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::sync::broadcast; - -const CLIENT_ID: &str = "00000000402b5328"; -const SCOPE: &str = "service::user.auth.xboxlive.com::MBI_SSL"; -const CACHE_FILE: &str = "msa_cache.json"; - -static DEVICE_CODE_TX: OnceLock> = OnceLock::new(); - -fn device_code_tx() -> broadcast::Sender { - DEVICE_CODE_TX.get_or_init(|| { - let (tx, _) = broadcast::channel(8); - tx - }).clone() -} - -pub fn subscribe_device_code() -> broadcast::Receiver { - device_code_tx().subscribe() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MinecraftProfile { - pub uuid: String, - pub username: String, - pub access_token: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct TokenCache { - access_token: String, - refresh_token: Option, - mc_uuid: Option, - mc_username: Option, - mc_access_token: Option, - expires_at: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct DeviceCodeResponse { - device_code: String, - user_code: String, - verification_uri: String, - expires_in: u64, - interval: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct TokenResponse { - access_token: String, - refresh_token: Option, - expires_in: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct TokenError { - error: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -struct DeviceAuthResponse { - token: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct McLoginResponse { - access_token: String, - username: String, - role: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct McProfileResponse { - id: String, - name: String, -} - -struct AuthKey { - signing: SigningKey, - verifying: p256::PublicKey, -} - -static AUTH_KEY: OnceLock = OnceLock::new(); - -fn auth_key() -> &'static AuthKey { - AUTH_KEY.get_or_init(|| { - let secret = SecretKey::random(&mut rand::rngs::OsRng); - let verifying = secret.public_key(); - let signing = secret.into(); - AuthKey { signing, verifying } - }) -} - -fn windows_timestamp() -> i64 { - let secs = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - ((secs + 11644473600) * 10_000_000) as i64 -} - -fn build_signature(method: &str, path: &str, body: &[u8]) -> String { - let key = auth_key(); - let ts = windows_timestamp(); - - let mut content = Vec::new(); - content.extend_from_slice(&1i32.to_be_bytes()); - content.push(0); - content.extend_from_slice(&ts.to_be_bytes()); - content.push(0); - content.extend_from_slice(method.as_bytes()); - content.push(0); - content.extend_from_slice(path.as_bytes()); - content.push(0); - content.push(0); - content.extend_from_slice(body); - content.push(0); - - let sig: Signature = key.signing.sign(&content); - let sig_bytes = sig.to_bytes(); - - let mut header = Vec::new(); - header.extend_from_slice(&1i32.to_be_bytes()); - header.extend_from_slice(&ts.to_be_bytes()); - header.extend_from_slice(&sig_bytes[..]); - - base64::engine::general_purpose::STANDARD.encode(&header) -} - -fn proof_key_value() -> serde_json::Value { - let key = auth_key(); - let encoded = key.verifying.to_encoded_point(false); - serde_json::json!({ - "kty": "EC", - "alg": "ES256", - "crv": "P-256", - "use": "sig", - "x": base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(encoded.x().unwrap()), - "y": base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(encoded.y().unwrap()), - }) -} - -fn cache_path() -> PathBuf { - let mut p = std::env::current_exe().unwrap_or_default(); - p.pop(); - p.push(CACHE_FILE); - p -} - -fn load_cache() -> Option { - let path = cache_path(); - std::fs::read_to_string(&path).ok().and_then(|s| serde_json::from_str(&s).ok()) -} - -fn save_cache(cache: &TokenCache) { - if let Ok(s) = serde_json::to_string(cache) { - let _ = std::fs::write(cache_path(), &s); - } -} - -async fn request_device_code() -> Result { - let client = reqwest::ClientBuilder::new() - .redirect(reqwest::redirect::Policy::none()) - .build() - .map_err(|e| format!("build client: {e}"))?; - let mut params = vec![ - ("client_id", CLIENT_ID), - ("scope", SCOPE), - ]; - params.push(("response_type", "device_code")); - let resp = client - .post("https://login.live.com/oauth20_connect.srf") - .form(¶ms) - .send() - .await - .map_err(|e| format!("device code request failed: {e}"))?; - let status = resp.status(); - let text = resp.text().await.map_err(|e| format!("read device code response: {e}"))?; - if !status.is_success() { - return Err(format!("device code HTTP {status}: {text}")); - } - let dc: DeviceCodeResponse = serde_json::from_str(&text) - .map_err(|e| format!("parse device code: {e} (raw: {text})"))?; - let msg = format!("{}|{}", dc.user_code, dc.verification_uri); - eprintln!("[MSA] device code: {msg}"); - let _ = device_code_tx().send(msg); - Ok(dc) -} - -async fn poll_token(device_code: &str) -> Result { - let client = reqwest::Client::new(); - loop { - let params = [ - ("client_id", CLIENT_ID), - ("grant_type", "device_code"), - ("device_code", device_code), - ]; - let resp = client - .post("https://login.live.com/oauth20_token.srf") - .form(¶ms) - .send() - .await - .map_err(|e| format!("token poll failed: {e}"))?; - let text = resp.text().await.map_err(|e| format!("read token response: {e}"))?; - if let Ok(tok) = serde_json::from_str::(&text) { - return Ok(tok); - } - if let Ok(err) = serde_json::from_str::(&text) { - if err.error == "authorization_pending" || err.error == "slow_down" { - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - continue; - } - return Err(format!("token error: {}", err.error)); - } - return Err(format!("unexpected token response: {text}")); - } -} - -async fn device_authenticate() -> Result { - let client = reqwest::Client::new(); - let device_id = format!("{{{}}}", uuid::Uuid::new_v4()); - let pk = proof_key_value(); - let body = serde_json::json!({ - "Properties": { - "DeviceType": "Win32", - "Id": device_id, - "AuthMethod": "ProofOfPossession", - "ProofKey": pk, - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT", - }); - let body_bytes = serde_json::to_vec(&body).map_err(|e| format!("serialize device body: {e}"))?; - let sig = build_signature("POST", "/device/authenticate", &body_bytes); - - let resp = client - .post("https://device.auth.xboxlive.com/device/authenticate") - .header("x-xbl-contract-version", "1") - .header("Signature", &sig) - .header("Accept", "application/json") - .body(body_bytes) - .send() - .await - .map_err(|e| format!("device authenticate: {e}"))?; - - let status = resp.status(); - let text = resp.text().await.map_err(|e| format!("device authenticate read: {e}"))?; - if !status.is_success() { - return Err(format!("device auth HTTP {status}: {text}")); - } - let auth: DeviceAuthResponse = serde_json::from_str(&text) - .map_err(|e| format!("parse device auth: {e} {text}"))?; - Ok(auth.token) -} - -async fn sisu_authorize(msa_token: &str, device_token: &str) -> Result<(String, String), String> { - let client = reqwest::Client::new(); - let pk = proof_key_value(); - let body = serde_json::json!({ - "Sandbox": "RETAIL", - "UseModernGamertag": true, - "AppId": CLIENT_ID, - "AccessToken": format!("t={msa_token}"), - "DeviceToken": device_token, - "ProofKey": pk, - "RelyingParty": "rp://api.minecraftservices.com/", - }); - let body_bytes = serde_json::to_vec(&body).map_err(|e| format!("serialize sisu body: {e}"))?; - let sig = build_signature("POST", "/authorize", &body_bytes); - - let resp = client - .post("https://sisu.xboxlive.com/authorize") - .header("Signature", &sig) - .header("Accept", "application/json") - .body(body_bytes) - .send() - .await - .map_err(|e| format!("sisu auth: {e}"))?; - - let status = resp.status(); - let text = resp.text().await.map_err(|e| format!("sisu auth read: {e}"))?; - if !status.is_success() { - return Err(format!("sisu auth HTTP {status}: {text}")); - } - - #[derive(Deserialize)] - struct SisuResp { - #[serde(rename = "UserToken")] - user_token: SisuTok, - #[serde(rename = "AuthorizationToken")] - auth_token: SisuTok, - } - #[derive(Deserialize)] - struct SisuTok { - #[serde(rename = "Token")] - token: String, - #[serde(rename = "DisplayClaims")] - claims: SisuClaims, - } - #[derive(Deserialize)] - struct SisuClaims { - xui: Vec, - } - #[derive(Deserialize)] - struct SisuXui { - uhs: String, - } - - let sisu: SisuResp = serde_json::from_str(&text) - .map_err(|e| format!("parse sisu: {e} {text}"))?; - let xsts_token = sisu.auth_token.token; - let uhs = sisu.user_token.claims.xui.first() - .ok_or_else(|| "no uhs in sisu user token".to_string())? - .uhs.clone(); - Ok((xsts_token, uhs)) -} - -async fn mc_login(xsts_token: &str, uhs: &str) -> Result<(String, String, String), String> { - let client = reqwest::Client::new(); - let identity = format!("XBL3.0 x={uhs};{xsts_token}"); - let body = serde_json::json!({ "identityToken": identity }); - let resp = client - .post("https://api.minecraftservices.com/authentication/login_with_xbox") - .json(&body) - .send() - .await - .map_err(|e| format!("mc login: {e}"))?; - let text = resp.text().await.map_err(|e| format!("mc login read: {e}"))?; - let login: McLoginResponse = serde_json::from_str(&text) - .map_err(|e| format!("parse mc login: {e} {text}"))?; - - let profile_resp = client - .get("https://api.minecraftservices.com/minecraft/profile") - .header("Authorization", format!("Bearer {}", login.access_token)) - .send() - .await - .map_err(|e| format!("mc profile: {e}"))?; - let ptext = profile_resp.text().await.map_err(|e| format!("mc profile read: {e}"))?; - let profile: McProfileResponse = serde_json::from_str(&ptext) - .map_err(|e| format!("parse mc profile: {e} {ptext}"))?; - - Ok((profile.id, profile.name, login.access_token)) -} - -pub async fn authenticate() -> Result { - if let Some(cached) = load_cache() { - if let (Some(uuid), Some(uname), Some(token)) = - (cached.mc_uuid, cached.mc_username, cached.mc_access_token) - { - return Ok(MinecraftProfile { uuid, username: uname, access_token: token }); - } - } - - let device = request_device_code().await?; - - let tok = poll_token(&device.device_code).await?; - let expires = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs() + tok.expires_in; - - let device_token = device_authenticate().await?; - let (xsts_token, uhs) = sisu_authorize(&tok.access_token, &device_token).await?; - let (uuid, username, mc_token) = mc_login(&xsts_token, &uhs).await?; - - let cache = TokenCache { - access_token: tok.access_token, - refresh_token: tok.refresh_token, - mc_uuid: Some(uuid.clone()), - mc_username: Some(username.clone()), - mc_access_token: Some(mc_token.clone()), - expires_at: expires, - }; - save_cache(&cache); - - Ok(MinecraftProfile { uuid, username, access_token: mc_token }) -} diff --git a/src-tauri/src/lce_bridge/registry.rs b/src-tauri/src/lce_bridge/registry.rs deleted file mode 100644 index 0cd2168..0000000 --- a/src-tauri/src/lce_bridge/registry.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::collections::HashMap; -use std::sync::OnceLock; - -static MAPPINGS: OnceLock = OnceLock::new(); - -pub fn registry() -> &'static MappingRegistry { - MAPPINGS.get_or_init(|| { - let mut r = MappingRegistry { - block_map: HashMap::new(), - biome_map: HashMap::new(), - fallback_block: u16::MAX, - }; - r.load_default(); - r - }) -} - -pub struct MappingRegistry { - block_map: HashMap, - biome_map: HashMap, - fallback_block: u16, -} - -impl MappingRegistry { - fn load_default(&mut self) { - for (java_id, lce_id) in DEFAULT_BLOCK_MAP { - self.block_map.insert(*java_id, *lce_id); - } - for (name, id) in DEFAULT_BIOME_MAP { - self.biome_map.insert(name.to_string(), *id); - } - self.fallback_block = (1 << 8) | 0; - } - - pub fn is_air(&self, java_state_id: i32) -> bool { - java_state_id == 0 || java_state_id == 1024 - } - - pub fn get_lce(&self, java_state_id: i32) -> u16 { - self.block_map.get(&java_state_id).copied().unwrap_or(self.fallback_block) - } - - pub fn get_lce_id(&self, java_state_id: i32) -> u8 { - (self.get_lce(java_state_id) >> 8) as u8 - } - - pub fn get_lce_data(&self, java_state_id: i32) -> u8 { - (self.get_lce(java_state_id) & 0xFF) as u8 - } - - pub fn get_biome(&self, java_name: &str) -> u8 { - self.biome_map.get(java_name).copied().unwrap_or(1) - } - - pub fn register_block(&mut self, java_state_id: i32, packed_lce: u16) { - self.block_map.insert(java_state_id, packed_lce); - } -} - -pub fn get_lce_block_id(java_state_id: i32) -> u8 { - registry().get_lce_id(java_state_id) -} - -pub fn get_lce_block_data(java_state_id: i32) -> u8 { - registry().get_lce_data(java_state_id) -} - -pub fn get_lce_biome(java_name: &str) -> u8 { - registry().get_biome(java_name) -} - -static DEFAULT_BLOCK_MAP: &[(i32, u16)] = &[ - (0, 0), (1, 1<<8|0), (2, 2<<8|0), (3, 3<<8|0), (4, 4<<8|0), - (5, 5<<8|0), (5, 5<<8|1), (5, 5<<8|2), (5, 5<<8|3), (5, 5<<8|4), (5, 5<<8|5), - (7, 7<<8|0), (8, 8<<8|0), (9, 9<<8|0), (10, 10<<8|0), (11, 11<<8|0), - (12, 12<<8|0), (13, 13<<8|0), (14, 14<<8|0), (15, 15<<8|0), - (16, 16<<8|0), (16, 16<<8|1), (16, 16<<8|2), (16, 16<<8|3), (16, 16<<8|4), (16, 16<<8|5), - (17, 17<<8|0), (17, 17<<8|1), (17, 17<<8|2), (17, 17<<8|3), - (18, 18<<8|0), (18, 18<<8|1), (18, 18<<8|2), (18, 18<<8|3), - (19, 19<<8|0), (19, 19<<8|1), (19, 19<<8|2), - (20, 20<<8|0), (21, 21<<8|0), - (22, 22<<8|0), (24, 24<<8|0), (24, 24<<8|1), (24, 24<<8|2), - (25, 25<<8|0), (35, 35<<8|0), (35, 35<<8|1), (35, 35<<8|2), (35, 35<<8|3), - (35, 35<<8|4), (35, 35<<8|5), (35, 35<<8|6), (35, 35<<8|7), - (35, 35<<8|8), (35, 35<<8|9), (35, 35<<8|10), (35, 35<<8|11), - (35, 35<<8|12), (35, 35<<8|13), (35, 35<<8|14), (35, 35<<8|15), - (41, 41<<8|0), (42, 42<<8|0), (43, 43<<8|0), (43, 43<<8|8), - (44, 44<<8|0), (44, 44<<8|1), (44, 44<<8|2), (44, 44<<8|3), - (44, 44<<8|4), (44, 44<<8|5), (44, 44<<8|6), (44, 44<<8|7), - (45, 45<<8|0), (46, 46<<8|0), (47, 47<<8|0), (48, 48<<8|0), - (49, 49<<8|0), (50, 50<<8|0), (50, 50<<8|1), (50, 50<<8|2), (50, 50<<8|3), (50, 50<<8|4), (50, 50<<8|5), - (52, 52<<8|0), (53, 53<<8|0), (53, 53<<8|1), (53, 53<<8|2), (53, 53<<8|3), - (56, 56<<8|0), (57, 57<<8|0), (58, 58<<8|0), (60, 60<<8|0), - (61, 61<<8|0), (62, 62<<8|0), (73, 73<<8|0), (74, 74<<8|0), - (78, 78<<8|0), (79, 79<<8|0), (80, 80<<8|0), (81, 81<<8|0), - (82, 82<<8|0), (83, 83<<8|0), (84, 84<<8|0), (85, 85<<8|0), - (86, 86<<8|0), (87, 87<<8|0), (88, 88<<8|0), (89, 89<<8|0), - (91, 91<<8|0), (95, 95<<8|0), (97, 97<<8|0), (98, 98<<8|0), - (98, 98<<8|1), (98, 98<<8|2), (98, 98<<8|3), - (99, 99<<8|0), (100, 100<<8|0), (101, 101<<8|0), (102, 102<<8|0), - (103, 103<<8|0), (107, 107<<8|0), (108, 108<<8|0), (109, 109<<8|0), - (110, 110<<8|0), (111, 111<<8|0), (112, 112<<8|0), (113, 113<<8|0), - (114, 114<<8|0), (121, 121<<8|0), (123, 123<<8|0), (124, 124<<8|0), - (125, 125<<8|0), (126, 126<<8|0), (128, 128<<8|0), (129, 129<<8|0), - (130, 130<<8|0), (133, 133<<8|0), (134, 134<<8|0), (135, 135<<8|0), - (136, 136<<8|0), (138, 138<<8|0), (139, 139<<8|0), (140, 140<<8|0), - (141, 141<<8|0), (142, 142<<8|0), (144, 144<<8|0), (145, 145<<8|0), - (147, 147<<8|0), (148, 148<<8|0), (151, 151<<8|0), (152, 152<<8|0), - (153, 153<<8|0), (154, 154<<8|0), (155, 155<<8|0), (156, 156<<8|0), - (158, 158<<8|0), (159, 159<<8|0), (159, 159<<8|1), (159, 159<<8|2), (159, 159<<8|3), - (159, 159<<8|4), (159, 159<<8|5), (159, 159<<8|6), (159, 159<<8|7), - (159, 159<<8|8), (159, 159<<8|9), (159, 159<<8|10), (159, 159<<8|11), - (159, 159<<8|12), (159, 159<<8|13), (159, 159<<8|14), (159, 159<<8|15), - (161, 161<<8|0), (161, 161<<8|1), (162, 162<<8|0), (162, 162<<8|1), - (163, 163<<8|0), (164, 164<<8|0), (165, 165<<8|0), (167, 167<<8|0), - (168, 168<<8|0), (168, 168<<8|1), (168, 168<<8|2), - (169, 169<<8|0), (170, 170<<8|0), (171, 171<<8|0), (171, 171<<8|1), - (171, 171<<8|2), (171, 171<<8|3), (171, 171<<8|4), (171, 171<<8|5), - (171, 171<<8|6), (171, 171<<8|7), (171, 171<<8|8), (171, 171<<8|9), - (171, 171<<8|10), (171, 171<<8|11), (171, 171<<8|12), (171, 171<<8|13), - (171, 171<<8|14), (171, 171<<8|15), - (172, 172<<8|0), (173, 173<<8|0), -]; - -static DEFAULT_BIOME_MAP: &[(&str, u8)] = &[ - ("minecraft:plains", 1), ("minecraft:desert", 2), ("minecraft:forest", 4), - ("minecraft:taiga", 5), ("minecraft:swamp", 6), ("minecraft:river", 7), - ("minecraft:frozen_river", 7), ("minecraft:ocean", 0), - ("minecraft:cold_ocean", 0), ("minecraft:deep_ocean", 0), - ("minecraft:frozen_ocean", 10), ("minecraft:deep_frozen_ocean", 10), - ("minecraft:beach", 16), ("minecraft:stone_beach", 16), - ("minecraft:snowy_beach", 16), ("minecraft:birch_forest", 27), - ("minecraft:dark_forest", 29), ("minecraft:flower_forest", 28), - ("minecraft:ice_spikes", 12), ("minecraft:jungle", 21), - ("minecraft:mushroom_fields", 14), ("minecraft:nether_wastes", 8), - ("minecraft:savanna", 36), ("minecraft:snowy_taiga", 30), - ("minecraft:snowy_plains", 11), ("minecraft:sunflower_plains", 1), - ("minecraft:the_end", 9), ("minecraft:the_void", 9), - ("minecraft:warm_ocean", 0), ("minecraft:lukewarm_ocean", 0), - ("minecraft:badlands", 39), ("minecraft:wooded_badlands", 39), - ("minecraft:eroded_badlands", 39), ("minecraft:meadow", 1), - ("minecraft:windswept_hills", 15), ("minecraft:windswept_forest", 15), - ("minecraft:windswept_gravelly_hills", 15), ("minecraft:windswept_savanna", 36), - ("minecraft:grove", 5), ("minecraft:snowy_slopes", 12), - ("minecraft:jagged_peaks", 24), ("minecraft:frozen_peaks", 24), - ("minecraft:stony_peaks", 15), ("minecraft:cherry_grove", 28), - ("minecraft:mangrove_swamp", 6), ("minecraft:deep_dark", 2), - ("minecraft:dripstone_caves", 2), ("minecraft:lush_caves", 2), -]; diff --git a/src-tauri/src/lce_bridge/util.rs b/src-tauri/src/lce_bridge/util.rs deleted file mode 100644 index 64fff3a..0000000 --- a/src-tauri/src/lce_bridge/util.rs +++ /dev/null @@ -1,226 +0,0 @@ -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use std::io::{Cursor, Read}; - -pub struct LceReader<'a> { - cursor: Cursor<&'a [u8]>, -} - -impl<'a> LceReader<'a> { - pub fn new(data: &'a [u8]) -> Self { - Self { cursor: Cursor::new(data) } - } - - pub fn remaining(&self) -> usize { - self.cursor.get_ref().len() - self.cursor.position() as usize - } - - pub fn read_u8(&mut self) -> std::io::Result { - self.cursor.read_u8() - } - - pub fn read_i8(&mut self) -> std::io::Result { - self.cursor.read_i8() - } - - pub fn read_u16(&mut self) -> std::io::Result { - self.cursor.read_u16::() - } - - pub fn read_i16(&mut self) -> std::io::Result { - self.cursor.read_i16::() - } - - pub fn read_u32(&mut self) -> std::io::Result { - self.cursor.read_u32::() - } - - pub fn read_i32(&mut self) -> std::io::Result { - self.cursor.read_i32::() - } - - pub fn read_u64(&mut self) -> std::io::Result { - self.cursor.read_u64::() - } - - pub fn read_i64(&mut self) -> std::io::Result { - self.cursor.read_i64::() - } - - pub fn read_f32(&mut self) -> std::io::Result { - self.cursor.read_f32::() - } - - pub fn read_f64(&mut self) -> std::io::Result { - self.cursor.read_f64::() - } - - pub fn read_bytes(&mut self, len: usize) -> std::io::Result> { - let mut buf = vec![0u8; len]; - self.cursor.read_exact(&mut buf)?; - Ok(buf) - } - - pub fn read_bool(&mut self) -> std::io::Result { - Ok(self.cursor.read_u8()? != 0) - } - - pub fn read_utf16(&mut self, max_length: u32) -> std::io::Result { - let len = self.read_u16()? as usize; - if len > max_length as usize { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, - format!("UTF-16 string too long: {} > {}", len, max_length))); - } - let mut chars = Vec::with_capacity(len); - for _ in 0..len { - chars.push(self.read_u16()?); - } - String::from_utf16(&chars) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) - } - - pub fn read_string_utf16(&mut self) -> std::io::Result { - self.read_utf16(0xFFFF) - } - - pub fn skip(&mut self, count: usize) -> std::io::Result<()> { - let pos = self.cursor.position() as usize + count; - if pos > self.cursor.get_ref().len() { - return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "skip past end")); - } - self.cursor.set_position(pos as u64); - Ok(()) - } -} - -pub struct LceWriter { - buf: Vec, -} - -impl LceWriter { - pub fn new() -> Self { - Self { buf: Vec::new() } - } - - pub fn into_bytes(self) -> Vec { - self.buf - } - - pub fn write_u8(&mut self, v: u8) { - let _ = self.buf.write_u8(v); - } - - pub fn write_i8(&mut self, v: i8) { - let _ = self.buf.write_i8(v); - } - - pub fn write_u16(&mut self, v: u16) { - let _ = self.buf.write_u16::(v); - } - - pub fn write_i16(&mut self, v: i16) { - let _ = self.buf.write_i16::(v); - } - - pub fn write_u32(&mut self, v: u32) { - let _ = self.buf.write_u32::(v); - } - - pub fn write_i32(&mut self, v: i32) { - let _ = self.buf.write_i32::(v); - } - - pub fn write_u64(&mut self, v: u64) { - let _ = self.buf.write_u64::(v); - } - - pub fn write_i64(&mut self, v: i64) { - let _ = self.buf.write_i64::(v); - } - - pub fn write_f32(&mut self, v: f32) { - let _ = self.buf.write_f32::(v); - } - - pub fn write_f64(&mut self, v: f64) { - let _ = self.buf.write_f64::(v); - } - - pub fn write_bytes(&mut self, data: &[u8]) { - self.buf.extend_from_slice(data); - } - - pub fn write_bool(&mut self, v: bool) { - self.write_u8(if v { 1 } else { 0 }); - } - - pub fn write_utf16(&mut self, s: &str) { - let encoded: Vec = s.encode_utf16().collect(); - self.write_u16(encoded.len() as u16); - for c in encoded { - self.write_u16(c); - } - } -} - -pub struct VarInt; - -impl VarInt { - pub fn read(reader: &mut &[u8]) -> std::io::Result { - let mut result = 0i32; - let mut shift = 0; - loop { - if reader.is_empty() { - return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "varint truncated")); - } - let byte = reader[0]; - *reader = &reader[1..]; - result |= ((byte & 0x7F) as i32) << shift; - shift += 7; - if byte & 0x80 == 0 { - return Ok(result); - } - } - } - - pub fn write_to(val: i32, buf: &mut Vec) { - let mut v = val as u32; - loop { - if v & !0x7F == 0 { - buf.push(v as u8); - return; - } - buf.push((v & 0x7F) as u8 | 0x80); - v >>= 7; - } - } - - pub fn write(val: i32) -> Vec { - let mut buf = Vec::new(); - let mut v = val as u32; - loop { - if v & !0x7F == 0 { - buf.push(v as u8); - return buf; - } - buf.push((v & 0x7F) as u8 | 0x80); - v >>= 7; - } - } - - pub fn encode_len(val: i32) -> usize { - let mut len = 1; - let mut v = val as u32 >> 7; - while v != 0 { - v >>= 7; - len += 1; - } - len - } -} - -pub fn read_varint_from_slice(data: &[u8]) -> std::io::Result<(i32, &[u8])> { - let mut reader = data; - let val = VarInt::read(&mut reader)?; - let consumed = data.len() - reader.len(); - Ok((val, &data[consumed..])) -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 857fde5..d9581a0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,14 +4,12 @@ mod config; mod util; mod platform; mod networking; -pub mod lce_bridge; mod workshop_server; mod commands; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; use tauri::Manager; -use commands::bridge as bridge_cmds; use commands::config as config_cmds; use commands::download; use commands::file_dialogs; @@ -66,9 +64,6 @@ pub fn run() { workshop::workshop_install, workshop::workshop_uninstall, workshop::workshop_list_installed, - bridge_cmds::bridge_start, - bridge_cmds::bridge_stop, - bridge_cmds::bridge_status, skin::get_screenshots, skin::delete_screenshot, skin::open_screenshot_folder, diff --git a/src/components/common/SkinViewer.tsx b/src/components/common/SkinViewer.tsx index c29560b..c731220 100644 --- a/src/components/common/SkinViewer.tsx +++ b/src/components/common/SkinViewer.tsx @@ -1,9 +1,7 @@ -import { useEffect, useRef, useState, memo, useCallback } from 'react'; -import { createPortal } from 'react-dom'; +import { useEffect, useRef, useState, memo } from 'react'; import { motion } from 'framer-motion'; import * as THREE from 'three'; import { useConfig } from '../../context/LauncherContext'; -import { TauriService } from '../../services/TauriService'; type UVSet = Record; @@ -28,57 +26,6 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo const capeRef = useRef(null); const requestRenderRef = useRef<(() => void) | null>(null); - const [showBridgeMenu, setShowBridgeMenu] = useState(false); - const [bridgeActive, setBridgeActive] = useState(false); - const [bridgeStarting, setBridgeStarting] = useState(false); - const [bridgeRemoteHost, setBridgeRemoteHost] = useState("127.0.0.1"); - const [bridgeRemotePort, setBridgeRemotePort] = useState("25565"); - const [bridgeProtocolVersion, setBridgeProtocolVersion] = useState(767); - const [bridgeAuthType, setBridgeAuthType] = useState("offline"); - const [msaCode, setMsaCode] = useState(""); - - useEffect(() => { - let unlisten: () => void; - (async () => { - const { listen } = await import("@tauri-apps/api/event"); - const u = await listen("lcebridge-msa-code", (e) => { - setMsaCode(e.payload); - }); - unlisten = u; - })(); - return () => { unlisten?.(); }; - }, []); - - const toggleBridge = useCallback(async () => { - if (bridgeActive) { - await TauriService.bridgeStop(); - setBridgeActive(false); - } else { - setBridgeStarting(true); - setMsaCode(""); - try { - await TauriService.bridgeStart( - "0.0.0.0", 25656, - bridgeRemoteHost, - parseInt(bridgeRemotePort) || 25565, - bridgeAuthType, - bridgeProtocolVersion, - ); - setBridgeActive(true); - } catch (e) { - console.error("Bridge start failed:", e); - } finally { - setBridgeStarting(false); - } - } - }, [bridgeActive, bridgeRemoteHost, bridgeRemotePort, bridgeProtocolVersion, bridgeAuthType]); - - useEffect(() => { - TauriService.bridgeStatus().then((s) => { - setBridgeActive(s === "running"); - }).catch(() => {}); - }, []); - useEffect(() => { if (!mountRef.current) return; const width = 260; @@ -267,21 +214,17 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo } const handleKeyDown = (e: KeyboardEvent) => { - if (showBridgeMenu) { - if (e.key === "Escape") setShowBridgeMenu(false); - return; - } if (document.activeElement?.tagName === 'INPUT') return; if (e.key === 'ArrowRight') { if (legacyMode) onNavigateRight(); - else if (focusIndex === 4) onNavigateRight(); - else if (focusIndex >= 1 && focusIndex < 4) setFocusIndex(prev => prev + 1); + else if (focusIndex === 3) onNavigateRight(); + else if (focusIndex >= 1 && focusIndex < 3) setFocusIndex(prev => prev + 1); } else if (e.key === 'ArrowLeft') { if (legacyMode) return; - if (focusIndex > 1 && focusIndex <= 4) setFocusIndex(prev => prev - 1); + if (focusIndex > 1 && focusIndex <= 3) setFocusIndex(prev => prev - 1); } else if (e.key === 'ArrowDown') { - setFocusIndex(prev => (prev < 4 ? prev + 1 : prev)); + setFocusIndex(prev => (prev < 3 ? prev + 1 : prev)); } else if (e.key === 'ArrowUp') { setFocusIndex(prev => (prev > 0 ? prev - 1 : prev)); } else if (e.key === 'Enter') { @@ -294,9 +237,6 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo playPressSound(); setActiveView('screenshots'); } else if (focusIndex === 3) { - playPressSound(); - setShowBridgeMenu(true); - } else if (focusIndex === 4) { playPressSound(); setActiveView('lcelive'); } @@ -305,7 +245,7 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [isFocusedSection, focusIndex, onNavigateRight, playPressSound, setActiveView, legacyMode, showBridgeMenu]); + }, [isFocusedSection, focusIndex, onNavigateRight, playPressSound, setActiveView, legacyMode]); useEffect(() => { if (isFocusedSection) { @@ -368,165 +308,15 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo - - {showBridgeMenu && createPortal( - setShowBridgeMenu(false)} - > -
e.stopPropagation()} - style={{ - backgroundImage: "url('/images/frame_background.png')", - backgroundSize: "100% 100%", - imageRendering: "pixelated", - }} - > -

- LCEBridge -

- -
-
- - {msaCode ? "Authenticating..." : bridgeActive ? "Running on port 25656" : "Stopped"} - -
- - {msaCode && ( -
- -
- {msaCode.split("|")[0]} -
-
- - Enter code at microsoft.com/link - - -
-
- )} - -
-
- - setBridgeRemoteHost(e.target.value)} - className="w-full bg-black/20 border-4 border-[#555] text-white p-3 text-lg outline-none focus:border-[#FFFF55] transition-colors mc-text-shadow" - disabled={bridgeActive} - /> -
-
- - setBridgeRemotePort(e.target.value)} - className="w-full bg-black/20 border-4 border-[#555] text-white p-3 text-lg outline-none focus:border-[#FFFF55] transition-colors mc-text-shadow" - disabled={bridgeActive} - /> -
-
- - setBridgeProtocolVersion(parseInt(e.target.value) || 767)} - className="w-full bg-black/20 border-4 border-[#555] text-white p-3 text-lg outline-none focus:border-[#FFFF55] transition-colors mc-text-shadow" - disabled={bridgeActive} - /> -
-
- -
- {(["offline", "online", "floodgate"] as const).map((t) => ( - - ))} -
-
-
- -
- - -
-
- , - document.body - )} ); }); diff --git a/src/hooks/useGameManager.ts b/src/hooks/useGameManager.ts index 30a8721..8046fb9 100644 --- a/src/hooks/useGameManager.ts +++ b/src/hooks/useGameManager.ts @@ -60,11 +60,6 @@ const BASE_EDITIONS = [ ]; const PARTNERSHIP_SERVERS = [ - { - name: "Emerald Java Server", - ip: "127.0.0.1", - port: 25656, - }, { name: "Kowhaifans Clubhouse", ip: "lce.kowhaifan.net", diff --git a/src/services/TauriService.ts b/src/services/TauriService.ts index b529c57..270172d 100644 --- a/src/services/TauriService.ts +++ b/src/services/TauriService.ts @@ -298,22 +298,4 @@ export class TauriService { return invoke("import_world", { inputPath, outputPath }); } - static async bridgeStart( - listenAddress: string, - port: number, - remoteHost: string, - remotePort: number, - authType: string, - remoteProtocolVersion: number, - ): Promise { - return invoke("bridge_start", { listenAddress, port, remoteHost, remotePort, authType, remoteProtocolVersion }); - } - - static async bridgeStop(): Promise { - return invoke("bridge_stop"); - } - - static async bridgeStatus(): Promise { - return invoke("bridge_status"); - } }