From 3a7335f43d608b8e879f9c2c6dc76a4b1d55ff0e Mon Sep 17 00:00:00 2001 From: neoapps-dev Date: Sun, 24 May 2026 11:27:21 +0300 Subject: [PATCH] chore: remove console2lce because IT DOESNT WORK --- src-tauri/src/commands/console2lce.rs | 59 +- src-tauri/src/console2lce/archive.rs | 192 ----- src-tauri/src/console2lce/block_mapping.rs | 176 ----- src-tauri/src/console2lce/chunk.rs | 155 ---- src-tauri/src/console2lce/convert.rs | 395 ---------- src-tauri/src/console2lce/error.rs | 33 - .../src/console2lce/lce_chunk_payload.rs | 234 ------ src-tauri/src/console2lce/lce_region.rs | 70 -- src-tauri/src/console2lce/level_dat.rs | 115 --- src-tauri/src/console2lce/mod.rs | 19 - src-tauri/src/console2lce/models.rs | 129 --- .../src/console2lce/modern_chunk_writer.rs | 737 ------------------ src-tauri/src/console2lce/nbt_util.rs | 453 ----------- src-tauri/src/console2lce/region.rs | 124 --- .../src/console2lce/save_data_container.rs | 54 -- src-tauri/src/console2lce/stfs.rs | 635 --------------- src-tauri/src/console2lce/world_profile.rs | 26 - src-tauri/src/lib.rs | 1 - src/components/modals/ImportWorldModal.tsx | 231 +----- src/services/RpcService.ts | 2 +- src/services/TauriService.ts | 9 +- 21 files changed, 33 insertions(+), 3816 deletions(-) delete mode 100644 src-tauri/src/console2lce/archive.rs delete mode 100644 src-tauri/src/console2lce/block_mapping.rs delete mode 100644 src-tauri/src/console2lce/chunk.rs delete mode 100644 src-tauri/src/console2lce/convert.rs delete mode 100644 src-tauri/src/console2lce/error.rs delete mode 100644 src-tauri/src/console2lce/lce_chunk_payload.rs delete mode 100644 src-tauri/src/console2lce/lce_region.rs delete mode 100644 src-tauri/src/console2lce/level_dat.rs delete mode 100644 src-tauri/src/console2lce/mod.rs delete mode 100644 src-tauri/src/console2lce/models.rs delete mode 100644 src-tauri/src/console2lce/modern_chunk_writer.rs delete mode 100644 src-tauri/src/console2lce/nbt_util.rs delete mode 100644 src-tauri/src/console2lce/region.rs delete mode 100644 src-tauri/src/console2lce/save_data_container.rs delete mode 100644 src-tauri/src/console2lce/stfs.rs delete mode 100644 src-tauri/src/console2lce/world_profile.rs diff --git a/src-tauri/src/commands/console2lce.rs b/src-tauri/src/commands/console2lce.rs index 4ab2da0..503749e 100644 --- a/src-tauri/src/commands/console2lce.rs +++ b/src-tauri/src/commands/console2lce.rs @@ -1,7 +1,6 @@ use std::path::Path; use std::fs; use tauri::AppHandle; -use crate::console2lce; #[tauri::command] #[allow(non_snake_case)] @@ -9,69 +8,17 @@ pub async fn import_world( _app: AppHandle, input_path: String, output_path: String, - profile: Option, - preserve_entities: Option, ) -> Result { - eprintln!("[import_world] called"); - eprintln!("[import_world] input_path: {:?}", input_path); - eprintln!("[import_world] output_path: {:?}", output_path); - eprintln!("[import_world] profile: {:?}", profile); - - let options = console2lce::ConversionOptions { - profile: profile.unwrap_or_else(|| "large".to_string()), - preserve_entities: preserve_entities.unwrap_or(false), - ..Default::default() - }; - - let input = Path::new(&input_path); - eprintln!("[import_world] input exists: {:?}", input.exists()); - eprintln!("[import_world] input is_dir: {:?}", input.is_dir()); - let output_parent = Path::new(&output_path).parent(); if let Some(parent) = output_parent { - eprintln!("[import_world] output parent: {:?}", parent); if !parent.exists() { - eprintln!("[import_world] creating output parent dir..."); fs::create_dir_all(parent) .map_err(|e| format!("Failed to create output directory {:?}: {}", parent, e))?; - eprintln!("[import_world] output parent dir created"); } } - let input_lower = input_path.to_lowercase(); - let result = if input_lower.ends_with(".ms") { - eprintln!("[import_world] detected .ms LCE save, copying directly"); - fs::copy(&input_path, &output_path) - .map_err(|e| format!("Failed to copy .ms file: {}", e))?; - console2lce::ConversionResult { - success: true, - message: "LCE save copied successfully".to_string(), - chunk_count: 0, - unknown_blocks: Vec::new(), - } - } else if input.is_dir() { - eprintln!("[import_world] detected Java world directory"); - console2lce::convert_java_world_to_lce(&input_path, &output_path, &options) - .map_err(|e| { - eprintln!("[import_world] Java world conversion FAILED: {}", e); - format!("Java world conversion failed: {}", e) - })? - } else { - eprintln!("[import_world] detected Xbox 360 / savegame file"); - console2lce::convert_xbox360_save_to_lce(&input_path, &output_path, &options) - .map_err(|e| { - eprintln!("[import_world] conversion FAILED: {}", e); - format!("Xbox/STFS conversion failed: {}", e) - })? - }; + fs::copy(&input_path, &output_path) + .map_err(|e| format!("Failed to copy .ms file: {}", e))?; - eprintln!("[import_world] conversion succeeded, {} chunks", result.chunk_count); - let mut msg = format!("World imported successfully!\nChunks converted: {}\nOutput: {}", - result.chunk_count, output_path); - - if !result.unknown_blocks.is_empty() { - msg.push_str(&format!("\n\nUnknown blocks (mapped to air):\n{}", result.unknown_blocks.join("\n"))); - } - - Ok(msg) + Ok(format!("World imported successfully!\nOutput: {}", output_path)) } diff --git a/src-tauri/src/console2lce/archive.rs b/src-tauri/src/console2lce/archive.rs deleted file mode 100644 index 19db59b..0000000 --- a/src-tauri/src/console2lce/archive.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::collections::HashMap; -use crate::console2lce::error::ConversionError; -use crate::console2lce::models::ArchiveEntry; -const ARCHIVE_MAGIC: [u8; 8] = [0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]; -pub fn parse_file_listing(data: &[u8]) -> Result>, ConversionError> { - if data.len() < 12 { - return Err(ConversionError::InvalidFormat("File listing too short".to_string())); - } - - let (header, le) = match try_parse_listing_header(data, true) { - Some(h) => (h, false), - None => match try_parse_listing_header(data, false) { - Some(h) => (h, true), - None => return Err(ConversionError::InvalidFormat("Invalid file listing header".to_string())), - }, - }; - - let footer_entry_size: usize = if header.latest <= 1 { 136 } else { 144 }; - let mut files = HashMap::new(); - for i in 0..header.file_count { - let entry_off = header.index_offset as usize + i as usize * footer_entry_size; - if entry_off + footer_entry_size > data.len() { - break; - } - - let name = decode_utf16le_padded(&data[entry_off..entry_off + 128]); - let size_off = entry_off + 128; - let ofs_off = size_off + 4; - let file_size = if le { - u32::from_le_bytes(data[size_off..size_off + 4].try_into().unwrap()) - } else { - u32::from_be_bytes(data[size_off..size_off + 4].try_into().unwrap()) - }; - - let file_ofs = if le { - u32::from_le_bytes(data[ofs_off..ofs_off + 4].try_into().unwrap()) - } else { - u32::from_be_bytes(data[ofs_off..ofs_off + 4].try_into().unwrap()) - }; - - if name.is_empty() { - continue; - } - - let start = file_ofs as usize; - let end = (start + file_size as usize).min(data.len()); - if start < end { - files.insert(name, data[start..end].to_vec()); - } - } - - Ok(files) -} - -struct ListingHeader { - index_offset: u32, - file_count: u32, - oldest: u16, - latest: u16, -} - -fn try_parse_listing_header(data: &[u8], be: bool) -> Option { - let (index_offset, file_count) = if be { - (u32::from_be_bytes(data[0..4].try_into().ok()?), - u32::from_be_bytes(data[4..8].try_into().ok()?)) - } else { - (u32::from_le_bytes(data[0..4].try_into().ok()?), - u32::from_le_bytes(data[4..8].try_into().ok()?)) - }; - let oldest = if be { - u16::from_be_bytes(data[8..10].try_into().ok()?) - } else { - u16::from_le_bytes(data[8..10].try_into().ok()?) - }; - let latest = if be { - u16::from_be_bytes(data[10..12].try_into().ok()?) - } else { - u16::from_le_bytes(data[10..12].try_into().ok()?) - }; - - if oldest > 13 || latest > 13 || latest < oldest { - return None; - } - if index_offset < 12 || index_offset as usize >= data.len() { - return None; - } - - let (file_count, _footer_size) = if latest <= 1 { - if file_count == 0 || file_count % 136 != 0 { - return None; - } - (file_count / 136, 136usize) - } else { - (file_count, 144usize) - }; - - if file_count > 32768 { - return None; - } - - Some(ListingHeader { index_offset, file_count, oldest, latest }) -} - -fn decode_utf16le_padded(bytes: &[u8]) -> String { - let mut chars = Vec::new(); - for chunk in bytes.chunks(2) { - if chunk.len() < 2 { break; } - let code_unit = u16::from_le_bytes([chunk[0], chunk[1]]); - if code_unit == 0 { break; } - if let Some(c) = char::from_u32(code_unit as u32) { - chars.push(c); - } - } - chars.into_iter().collect() -} -pub fn is_minecraft_360_archive(data: &[u8]) -> bool { - data.len() >= 8 && data[0..8] == ARCHIVE_MAGIC -} - -pub fn parse_archive(data: &[u8]) -> Result, ConversionError> { - if data.len() < 8 { - return Err(ConversionError::InvalidFormat("Archive data too short".to_string())); - } - if !is_minecraft_360_archive(data) { - return Err(ConversionError::InvalidFormat("Invalid Minecraft 360 archive magic".to_string())); - } - - let num_entries = read_be_u32(&data[8..12]) as usize; - let _header_size = read_be_u32(&data[12..16]) as usize; - let mut offset = 16usize; - let mut entries = HashMap::new(); - for _ in 0..num_entries { - if offset + 144 > data.len() { - break; - } - - let name_bytes = &data[offset..offset + 128]; - let name = decode_utf16_be_padded(name_bytes); - let length = read_be_u32(&data[offset + 128..offset + 132]); - let file_offset = read_be_u32(&data[offset + 132..offset + 136]) as u64; - let timestamp = read_be_u64(&data[offset + 136..offset + 144]); - offset += 144; - entries.insert(name.clone(), ArchiveEntry { - name, - offset: file_offset, - length, - timestamp, - }); - } - - Ok(entries) -} - -fn decode_utf16_be_padded(bytes: &[u8]) -> String { - let mut chars = Vec::new(); - for chunk in bytes.chunks(2) { - if chunk.len() < 2 { - break; - } - let code_unit = u16::from_be_bytes([chunk[0], chunk[1]]); - if code_unit == 0 { - break; - } - if let Some(c) = char::from_u32(code_unit as u32) { - chars.push(c); - } - } - chars.into_iter().collect() -} - -fn read_be_u32(bytes: &[u8]) -> u32 { - if bytes.len() < 4 { return 0; } - ((bytes[0] as u32) << 24) | ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32) -} - -fn read_be_u64(bytes: &[u8]) -> u64 { - if bytes.len() < 8 { return 0; } - ((bytes[0] as u64) << 56) | ((bytes[1] as u64) << 48) | ((bytes[2] as u64) << 40) | ((bytes[3] as u64) << 32) - | ((bytes[4] as u64) << 24) | ((bytes[5] as u64) << 16) | ((bytes[6] as u64) << 8) | (bytes[7] as u64) -} - -pub fn extract_file_from_archive(data: &[u8], entry: &ArchiveEntry) -> Result, ConversionError> { - let start = entry.offset as usize; - let end = start + entry.length as usize; - if end > data.len() { - return Err(ConversionError::InvalidFormat(format!( - "Archive entry '{}' at offset {} with length {} exceeds total size {}", - entry.name, entry.offset, entry.length, data.len() - ))); - } - Ok(data[start..end].to_vec()) -} diff --git a/src-tauri/src/console2lce/block_mapping.rs b/src-tauri/src/console2lce/block_mapping.rs deleted file mode 100644 index 7fe9bb5..0000000 --- a/src-tauri/src/console2lce/block_mapping.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::collections::HashMap; -use once_cell::sync::Lazy; -use crate::console2lce::models::LegacyBlockState; -pub static MODERN_DIRECT_MAP: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - m.insert("stone", LegacyBlockState::new(1, 0)); - m.insert("granite", LegacyBlockState::new(1, 1)); - m.insert("polished_granite", LegacyBlockState::new(1, 2)); - m.insert("diorite", LegacyBlockState::new(1, 3)); - m.insert("polished_diorite", LegacyBlockState::new(1, 4)); - m.insert("andesite", LegacyBlockState::new(1, 5)); - m.insert("polished_andesite", LegacyBlockState::new(1, 6)); - m.insert("grass_block", LegacyBlockState::new(2, 0)); - m.insert("dirt", LegacyBlockState::new(3, 0)); - m.insert("coarse_dirt", LegacyBlockState::new(3, 1)); - m.insert("podzol", LegacyBlockState::new(3, 2)); - m.insert("cobblestone", LegacyBlockState::new(4, 0)); - m.insert("bedrock", LegacyBlockState::new(7, 0)); - m.insert("sand", LegacyBlockState::new(12, 0)); - m.insert("red_sand", LegacyBlockState::new(12, 1)); - m.insert("gravel", LegacyBlockState::new(13, 0)); - m.insert("gold_ore", LegacyBlockState::new(14, 0)); - m.insert("iron_ore", LegacyBlockState::new(15, 0)); - m.insert("coal_ore", LegacyBlockState::new(16, 0)); - m.insert("sponge", LegacyBlockState::new(19, 0)); - m.insert("wet_sponge", LegacyBlockState::new(19, 1)); - m.insert("glass", LegacyBlockState::new(20, 0)); - m.insert("lapis_ore", LegacyBlockState::new(21, 0)); - m.insert("lapis_block", LegacyBlockState::new(22, 0)); - m.insert("sandstone", LegacyBlockState::new(24, 0)); - m.insert("chiseled_sandstone", LegacyBlockState::new(24, 1)); - m.insert("smooth_sandstone", LegacyBlockState::new(24, 2)); - m.insert("note_block", LegacyBlockState::new(25, 0)); - m.insert("cobweb", LegacyBlockState::new(30, 0)); - m.insert("short_grass", LegacyBlockState::new(31, 0)); - m.insert("fern", LegacyBlockState::new(31, 1)); - m.insert("dead_bush", LegacyBlockState::new(32, 0)); - m.insert("dandelion", LegacyBlockState::new(37, 0)); - m.insert("brown_mushroom", LegacyBlockState::new(39, 0)); - m.insert("red_mushroom", LegacyBlockState::new(40, 0)); - m.insert("gold_block", LegacyBlockState::new(41, 0)); - m.insert("iron_block", LegacyBlockState::new(42, 0)); - m.insert("bricks", LegacyBlockState::new(45, 0)); - m.insert("tnt", LegacyBlockState::new(46, 0)); - m.insert("bookshelf", LegacyBlockState::new(47, 0)); - m.insert("mossy_cobblestone", LegacyBlockState::new(48, 0)); - m.insert("obsidian", LegacyBlockState::new(49, 0)); - m.insert("torch", LegacyBlockState::new(50, 0)); - m.insert("fire", LegacyBlockState::new(51, 0)); - m.insert("mob_spawner", LegacyBlockState::new(52, 0)); - m.insert("chest", LegacyBlockState::new(54, 0)); - m.insert("trapped_chest", LegacyBlockState::new(146, 0)); - m.insert("diamond_ore", LegacyBlockState::new(56, 0)); - m.insert("diamond_block", LegacyBlockState::new(57, 0)); - m.insert("crafting_table", LegacyBlockState::new(58, 0)); - m.insert("wheat", LegacyBlockState::new(59, 0)); - m.insert("farmland", LegacyBlockState::new(60, 0)); - m.insert("furnace", LegacyBlockState::new(61, 0)); - m.insert("ladder", LegacyBlockState::new(65, 0)); - m.insert("rail", LegacyBlockState::new(66, 0)); - m.insert("snow", LegacyBlockState::new(78, 0)); - m.insert("ice", LegacyBlockState::new(79, 0)); - m.insert("snow_block", LegacyBlockState::new(80, 0)); - m.insert("cactus", LegacyBlockState::new(81, 0)); - m.insert("clay", LegacyBlockState::new(82, 0)); - m.insert("sugar_cane", LegacyBlockState::new(83, 0)); - m.insert("jukebox", LegacyBlockState::new(84, 0)); - m.insert("pumpkin", LegacyBlockState::new(86, 0)); - m.insert("netherrack", LegacyBlockState::new(87, 0)); - m.insert("soul_sand", LegacyBlockState::new(88, 0)); - m.insert("glowstone", LegacyBlockState::new(89, 0)); - m.insert("jack_o_lantern", LegacyBlockState::new(91, 0)); - m.insert("cake", LegacyBlockState::new(92, 0)); - m.insert("melon", LegacyBlockState::new(103, 0)); - m.insert("vine", LegacyBlockState::new(106, 0)); - m.insert("mycelium", LegacyBlockState::new(110, 0)); - m.insert("lily_pad", LegacyBlockState::new(111, 0)); - m.insert("nether_bricks", LegacyBlockState::new(112, 0)); - m.insert("nether_brick_fence", LegacyBlockState::new(113, 0)); - m.insert("enchanting_table", LegacyBlockState::new(116, 0)); - m.insert("brewing_stand", LegacyBlockState::new(117, 0)); - m.insert("cauldron", LegacyBlockState::new(118, 0)); - m.insert("end_portal", LegacyBlockState::new(119, 0)); - m.insert("end_portal_frame", LegacyBlockState::new(120, 0)); - m.insert("end_stone", LegacyBlockState::new(121, 0)); - m.insert("dragon_egg", LegacyBlockState::new(122, 0)); - m.insert("cocoa", LegacyBlockState::new(127, 0)); - m.insert("emerald_ore", LegacyBlockState::new(129, 0)); - m.insert("ender_chest", LegacyBlockState::new(130, 0)); - m.insert("tripwire_hook", LegacyBlockState::new(131, 0)); - m.insert("tripwire", LegacyBlockState::new(132, 0)); - m.insert("emerald_block", LegacyBlockState::new(133, 0)); - m.insert("command_block", LegacyBlockState::new(137, 0)); - m.insert("beacon", LegacyBlockState::new(138, 0)); - m.insert("flower_pot", LegacyBlockState::new(140, 0)); - m.insert("carrots", LegacyBlockState::new(141, 0)); - m.insert("potatoes", LegacyBlockState::new(142, 0)); - m.insert("anvil", LegacyBlockState::new(145, 0)); - m.insert("daylight_detector", LegacyBlockState::new(151, 0)); - m.insert("redstone_block", LegacyBlockState::new(152, 0)); - m.insert("nether_quartz_ore", LegacyBlockState::new(153, 0)); - m.insert("hopper", LegacyBlockState::new(154, 0)); - m.insert("slime_block", LegacyBlockState::new(165, 0)); - m.insert("iron_trapdoor", LegacyBlockState::new(167, 0)); - m.insert("sea_lantern", LegacyBlockState::new(169, 0)); - m.insert("hay_block", LegacyBlockState::new(170, 0)); - m.insert("dispenser", LegacyBlockState::new(23, 0)); - m.insert("dropper", LegacyBlockState::new(158, 0)); - m.insert("sticky_piston", LegacyBlockState::new(29, 0)); - m.insert("piston", LegacyBlockState::new(33, 0)); - m.insert("piston_head", LegacyBlockState::new(34, 0)); - m.insert("redstone_lamp", LegacyBlockState::new(123, 0)); - m.insert("red_nether_bricks", LegacyBlockState::new(112, 0)); - m.insert("bone_block", LegacyBlockState::new(1, 0)); - m.insert("observer", LegacyBlockState::new(1, 0)); - m.insert("shulker_box", LegacyBlockState::new(35, 0)); - m.insert("white_shulker_box", LegacyBlockState::new(35, 0)); - m.insert("orange_shulker_box", LegacyBlockState::new(35, 1)); - m.insert("magenta_shulker_box", LegacyBlockState::new(35, 2)); - m.insert("light_blue_shulker_box", LegacyBlockState::new(35, 3)); - m.insert("yellow_shulker_box", LegacyBlockState::new(35, 4)); - m.insert("lime_shulker_box", LegacyBlockState::new(35, 5)); - m.insert("pink_shulker_box", LegacyBlockState::new(35, 6)); - m.insert("gray_shulker_box", LegacyBlockState::new(35, 7)); - m.insert("light_gray_shulker_box", LegacyBlockState::new(35, 8)); - m.insert("cyan_shulker_box", LegacyBlockState::new(35, 9)); - m.insert("purple_shulker_box", LegacyBlockState::new(35, 10)); - m.insert("blue_shulker_box", LegacyBlockState::new(35, 11)); - m.insert("brown_shulker_box", LegacyBlockState::new(35, 12)); - m.insert("green_shulker_box", LegacyBlockState::new(35, 13)); - m.insert("red_shulker_box", LegacyBlockState::new(35, 14)); - m.insert("black_shulker_box", LegacyBlockState::new(35, 15)); - m -}); - -pub static COLOR_NAMES: &[&str] = &[ - "white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", - "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black", -]; - -pub fn get_color_name(color: &str) -> u8 { - match color { - "white" => 0, - "orange" => 1, - "magenta" => 2, - "light_blue" => 3, - "yellow" => 4, - "lime" => 5, - "pink" => 6, - "gray" => 7, - "light_gray" | "silver" => 8, - "cyan" => 9, - "purple" => 10, - "blue" => 11, - "brown" => 12, - "green" => 13, - "red" => 14, - "black" => 15, - _ => 0, - } -} - -pub fn get_color_data(color: &str) -> u8 { - get_color_name(color) -} - -pub fn get_wood_data(wood_type: &str) -> u8 { - match wood_type { - "spruce" => 1, - "birch" => 2, - "jungle" => 3, - "acacia" => 4, - "dark_oak" => 5, - _ => 0, - } -} diff --git a/src-tauri/src/console2lce/chunk.rs b/src-tauri/src/console2lce/chunk.rs deleted file mode 100644 index 4214fa1..0000000 --- a/src-tauri/src/console2lce/chunk.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::io::Read; -use flate2::read::ZlibDecoder; -use crate::console2lce::error::ConversionError; -use crate::console2lce::models::LegacyBlockState; -pub fn decode_xbox360_chunk(chunk_data: &[u8]) -> Result, ConversionError> { - if chunk_data.len() < 5 { - return Err(ConversionError::InvalidFormat("Chunk data too short".to_string())); - } - - let _compressed_size = read_be_u32(&chunk_data[0..4]); - let compression_scheme = chunk_data[4]; - let payload = &chunk_data[5..]; - match compression_scheme { - 0 => Ok(payload.to_vec()), - 1 => { - let mut decoder = ZlibDecoder::new(payload); - let mut decompressed = Vec::new(); - decoder.read_to_end(&mut decompressed)?; - Ok(decompressed) - } - 2 => { - let is_rle = payload.len() >= 4 && (payload[0..4].iter().any(|&b| b == 0xFF)); - if is_rle { - decode_rle_chunk(payload) - } else { - Ok(payload.to_vec()) - } - } - _ => Err(ConversionError::DecompressionFailed( - format!("Unknown chunk compression scheme: {}", compression_scheme) - )), - } -} - -fn decode_rle_chunk(data: &[u8]) -> Result, ConversionError> { - let mut output = Vec::new(); - let mut pos = 0; - while pos < data.len() { - let b = data[pos]; - pos += 1; - if b != 0xFF { - output.push(b); - } else { - if pos + 1 >= data.len() { - break; - } - let run_len = data[pos] as usize; - let value = data[pos + 1]; - pos += 2; - for _ in 0..run_len { - output.push(value); - } - } - } - - Ok(output) -} - -pub fn try_decode_compressed_tile_storage(data: &[u8]) -> Result, ConversionError> { - if data.len() < 2048 { - return Err(ConversionError::InvalidFormat("Compressed tile storage data too short".to_string())); - } - - let mut blocks: Vec = Vec::with_capacity(16384); - let index_table = &data[0..2048]; - let mut data_offset = 2048; - for entry in index_table.chunks(2) { - let index_value = u16::from_be_bytes([entry[0], entry[1]]); - let data_type = (index_value >> 13) & 0x07; - let data_offset_val = (index_value & 0x1FFF) as usize; - match data_type { - 0 => { - for _ in 0..16 { - blocks.push(LegacyBlockState::new(0, 0)); - } - } - 1 => { - if data_offset + 4 <= data.len() { - for i in 0..16 { - let nibble = (data[data_offset + i / 2] >> (if i % 2 == 0 { 0 } else { 4 })) & 0x0F; - blocks.push(LegacyBlockState::new(0, nibble as u8)); - } - data_offset += 4; - } - } - 2 => { - if data_offset + 8 <= data.len() { - for i in 0..16 { - let nibble = if data_offset + i < data.len() { - data[data_offset + i] - } else { - 0 - }; - blocks.push(LegacyBlockState::new(nibble, 0)); - } - data_offset += 8; - } - } - 3 => { - if data_offset + 16 <= data.len() { - for i in 0..16 { - if data_offset + i * 2 + 1 < data.len() { - let id = data[data_offset + i * 2]; - let meta = data[data_offset + i * 2 + 1]; - blocks.push(LegacyBlockState::new(id, meta)); - } else { - blocks.push(LegacyBlockState::new(0, 0)); - } - } - data_offset += 16; - } - } - 4 => { - if data_offset_val > 0 && data_offset_val < data.len() { - let sparse_offset = data_offset_val; - let mut sparse_pos = sparse_offset; - while sparse_pos < data.len() && blocks.len() < 16384 { - if sparse_pos + 3 > data.len() { - break; - } - let location = data[sparse_pos] as usize; - let block_id = data[sparse_pos + 1]; - let block_data = data[sparse_pos + 2]; - sparse_pos += 3; - for _ in blocks.len()..location { - blocks.push(LegacyBlockState::new(0, 0)); - } - if blocks.len() < 16384 { - blocks.push(LegacyBlockState::new(block_id, block_data)); - } - } - while blocks.len() < 16384 { - blocks.push(LegacyBlockState::new(0, 0)); - } - } else { - for _ in 0..16 { - blocks.push(LegacyBlockState::new(0, 0)); - } - } - } - _ => { - for _ in 0..16 { - blocks.push(LegacyBlockState::new(0, 0)); - } - } - } - } - - Ok(blocks) -} - -fn read_be_u32(bytes: &[u8]) -> u32 { - if bytes.len() < 4 { return 0; } - ((bytes[0] as u32) << 24) | ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32) -} diff --git a/src-tauri/src/console2lce/convert.rs b/src-tauri/src/console2lce/convert.rs deleted file mode 100644 index 2e1b5bc..0000000 --- a/src-tauri/src/console2lce/convert.rs +++ /dev/null @@ -1,395 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; -use std::fs; -use crate::console2lce::error::ConversionError; -use crate::console2lce::models::*; -use crate::console2lce::nbt_util::{self, NbtHelper}; -use crate::console2lce::modern_chunk_writer; -use crate::console2lce::lce_chunk_payload; -use crate::console2lce::lce_region::LceRegionFile; -use crate::console2lce::level_dat; -use crate::console2lce::save_data_container; -use crate::console2lce::world_profile; -pub fn convert_xbox360_save_to_lce( - input_path: &str, - output_path: &str, - _options: &ConversionOptions, -) -> Result { - eprintln!("[convert] convert_xbox360_save_to_lce start"); - eprintln!("[convert] input_path = {:?}", input_path); - eprintln!("[convert] output_path = {:?}", output_path); - let data = std::fs::read(input_path) - .map_err(|e| { - eprintln!("[convert] ERROR reading input: {}", e); - ConversionError::Io(e) - })?; - eprintln!("[convert] read {} bytes from input", data.len()); - let decompressed = if crate::console2lce::stfs::is_stfs_package(&data) { - eprintln!("[convert] detected STFS package, extracting savegame.dat..."); - let savegame_dat = crate::console2lce::stfs::extract_savegame_from_stfs(&data) - .map_err(|e| { - eprintln!("[convert] ERROR extracting from STFS: {}", e); - ConversionError::InvalidFormat(format!("STFS extraction failed: {}", e)) - })?; - eprintln!("[convert] savegame.dat extracted, {} bytes", savegame_dat.len()); - eprintln!("[convert] decompressing STFS savegame data..."); - crate::console2lce::stfs::try_decompress_stfs_savegame(&savegame_dat) - .map_err(|e| { - eprintln!("[convert] ERROR decompressing STFS savegame: {}", e); - ConversionError::DecompressionFailed(format!("STFS savegame decompression failed: {}", e)) - })? - } else if (data.len() >= 8 && data[0..8] == [0x0B, 0xFC, 0x4A, 0x46, 0xAE, 0x7A, 0x43, 0x2B]) || input_path.ends_with(".dat") { - eprintln!("[convert] detected raw savegame.dat, decompressing directly..."); - crate::console2lce::stfs::try_decompress_savegame(&data) - .map_err(|e| { - eprintln!("[convert] ERROR decompressing savegame: {}", e); - ConversionError::DecompressionFailed(format!("Savegame decompression failed: {}", e)) - })? - } else { - return Err(ConversionError::InvalidFormat( - "Not an STFS package (.bin) or savegame.dat (.dat)".to_string() - )); - }; - eprintln!("[convert] decompressed {} bytes", decompressed.len()); - eprintln!("[convert] parsing file listing..."); - let files = crate::console2lce::archive::parse_file_listing(&decompressed) - .map_err(|e| { - eprintln!("[convert] ERROR parsing file listing: {}", e); - ConversionError::InvalidFormat(format!("File listing parse failed: {}", e)) - })?; - eprintln!("[convert] file listing has {} entries", files.len()); - let mut level_dat_data: Vec = Vec::new(); - let mut regions: HashMap> = HashMap::new(); - let mut total_chunks = 0; - for (name, file_data) in &files { - eprintln!("[convert] file: {:?} ({} bytes)", name, file_data.len()); - if name == "level.dat" || name.ends_with("/level.dat") { - level_dat_data = file_data.clone(); - } else if name.ends_with(".mcr") || name.ends_with(".mca") { - if let Ok(chunks) = crate::console2lce::region::read_java_mcr_region(file_data) { - eprintln!("[convert] region {} has {} chunks", name, chunks.len()); - let mut dim_region = LceRegionFile::new(); - for (local_x, local_z, chunk_raw) in &chunks { - if let Ok(decompressed) = crate::console2lce::nbt_util::read_mcr_chunk(chunk_raw, 0) { - if let Ok((_name, root_tag)) = crate::console2lce::nbt_util::read_nbt(&decompressed) { - let level = root_tag.as_compound() - .and_then(|m| m.get("Level").and_then(|t| t.as_compound())) - .or_else(|| root_tag.as_compound()) - .cloned() - .unwrap_or_default(); - let cx = crate::console2lce::nbt_util::NbtHelper::get_int(&level, "xPos").unwrap_or(*local_x as i32); - let cz = crate::console2lce::nbt_util::NbtHelper::get_int(&level, "zPos").unwrap_or(*local_z as i32); - if let Ok(payload) = crate::console2lce::lce_chunk_payload::encode_legacy_nbt( - cx, cz, - &[0u8; CHUNK_BLOCKS], &[0u8; CHUNK_NIBBLES], - &[0u8; CHUNK_NIBBLES], &[0u8; CHUNK_NIBBLES], - &[0u8; 256], &[1u8; 256], - 0, 0, - Vec::new(), Vec::new(), None, - ) { - let _ = dim_region.add_chunk(cx, cz, &payload); - total_chunks += 1; - } - } - } - } - if let Ok(data) = dim_region.build() { - let region_key = format!("region/{}", name); - regions.insert(region_key, data); - } - } - } - } - - eprintln!("[convert] writing output to {:?}", output_path); - if total_chunks == 0 { - eprintln!("[convert] no chunks found. copying input directly"); - if let Some(parent) = std::path::Path::new(output_path).parent() { - let _ = std::fs::create_dir_all(parent); - } - std::fs::write(output_path, &data) - .map_err(ConversionError::Io)?; - return Ok(ConversionResult { - success: true, - message: "No chunks found, copied raw data".to_string(), - chunk_count: 0, - unknown_blocks: Vec::new(), - }); - } - - let ms_data = save_data_container::write_save_data_ms(&level_dat_data, ®ions) - .map_err(|e| { - eprintln!("[convert] ERROR building saveData.ms: {}", e); - e - })?; - - if let Some(parent) = std::path::Path::new(output_path).parent() { - let _ = std::fs::create_dir_all(parent); - } - std::fs::write(output_path, &ms_data) - .map_err(ConversionError::Io)?; - - eprintln!("[convert] convert_xbox360_save_to_lce done, {} chunks", total_chunks); - Ok(ConversionResult { - success: true, - message: format!("Converted {} chunks from Xbox 360 save", total_chunks), - chunk_count: total_chunks, - unknown_blocks: Vec::new(), - }) -} - -pub fn convert_java_world_to_lce( - world_path: &str, - output_path: &str, - options: &ConversionOptions, -) -> Result { - eprintln!("[convert] convert_java_world_to_lce start"); - eprintln!("[convert] world_path = {:?}", world_path); - eprintln!("[convert] output_path = {:?}", output_path); - eprintln!("[convert] profile = {:?}", options.profile); - let _profile = world_profile::get_profile(&options.profile) - .ok_or_else(|| { - eprintln!("[convert] ERROR: unknown profile {:?}", options.profile); - ConversionError::InvalidFormat(format!("Unknown profile: {}", options.profile)) - })?; - - let world_dir = Path::new(world_path); - eprintln!("[convert] world_dir resolved to {:?}", world_dir); - eprintln!("[convert] world_dir.is_dir() = {:?}", world_dir.is_dir()); - if !world_dir.is_dir() { - eprintln!("[convert] ERROR: world directory not found at {:?}", world_dir); - eprintln!("[convert] Checking existence: {:?}", world_dir.exists()); - return Err(ConversionError::InvalidFormat(format!("World directory not found: {}", world_path))); - } - - let level_dat_path = world_dir.join("level.dat"); - eprintln!("[convert] reading level.dat: {:?}", level_dat_path); - eprintln!("[convert] level.dat exists: {:?}", level_dat_path.exists()); - let level_dat_data = fs::read(&level_dat_path) - .map_err(|e| { - eprintln!("[convert] ERROR reading level.dat: {}", e); - e - })?; - eprintln!("[convert] level.dat size: {} bytes", level_dat_data.len()); - let mut context = ChunkConversionContext::new(); - context.preserve_dynamic_chunk_data = options.preserve_entities; - eprintln!("[convert] converting level.dat..."); - let lce_level_dat = level_dat::convert_level_dat( - &level_dat_data, &options.profile, &options.game_version, &mut context, - )?; - eprintln!("[convert] level.dat converted, size: {} bytes", lce_level_dat.len()); - let dimensions = vec!["region".to_string(), "DIM-1/region".to_string(), "DIM1/region".to_string()]; - let mut regions: HashMap> = HashMap::new(); - let mut total_chunks = 0; - for dim_dir in &dimensions { - let dim_path = world_dir.join(dim_dir); - eprintln!("[convert] checking dimension dir: {:?} (exists: {:?})", dim_path, dim_path.is_dir()); - if !dim_path.is_dir() { - eprintln!("[convert] skipping (not a directory)"); - continue; - } - - let mut dim_region = LceRegionFile::new(); - let entries = match fs::read_dir(&dim_path) { - Ok(e) => e, - Err(e) => { - eprintln!("[convert] ERROR reading dim dir {:?}: {}", dim_path, e); - continue; - } - }; - - for entry in entries { - let entry = match entry { - Ok(e) => e, - Err(e) => { - eprintln!("[convert] ERROR reading dir entry: {}", e); - continue; - } - }; - let path = entry.path(); - let file_name = path.file_name() - .and_then(|n| n.to_str()) - .unwrap_or("") - .to_string(); - - if !file_name.ends_with(".mca") && !file_name.ends_with(".mcr") { - eprintln!("[convert] skipping non-region file: {:?}", path); - continue; - } - - eprintln!("[convert] processing region file: {:?}", path); - let region_data = match fs::read(&path) { - Ok(d) => { - eprintln!("[convert] read {} bytes", d.len()); - d - } - Err(e) => { - eprintln!("[convert] ERROR reading region file: {}", e); - continue; - } - }; - - let chunks = match crate::console2lce::region::read_java_mcr_region(®ion_data) { - Ok(c) => { - eprintln!("[convert] found {} chunks", c.len()); - c - } - Err(e) => { - eprintln!("[convert] ERROR parsing region: {}", e); - continue; - } - }; - - for (local_x, local_z, chunk_raw) in &chunks { - eprintln!("[convert] converting chunk ({}, {})", local_x, local_z); - let decompressed = match nbt_util::read_mcr_chunk(chunk_raw, 0) { - Ok(d) => { - eprintln!("[convert] decompressed {} bytes", d.len()); - d - } - Err(e) => { - eprintln!("[convert] ERROR decompressing chunk: {}", e); - continue; - } - }; - - let (name, root_tag) = match nbt_util::read_nbt(&decompressed) { - Ok(v) => v, - Err(e) => { - eprintln!("[convert] ERROR parsing chunk NBT: {}", e); - continue; - } - }; - eprintln!("[convert] chunk root name: {:?}", name); - let level = root_tag.as_compound() - .and_then(|m| m.get("Level").and_then(|t| t.as_compound())) - .or_else(|| root_tag.as_compound()) - .cloned() - .unwrap_or_default(); - - let source_chunk_x = NbtHelper::get_int(&level, "xPos").unwrap_or(0); - let source_chunk_z = NbtHelper::get_int(&level, "zPos").unwrap_or(0); - eprintln!("[convert] source chunk pos: ({}, {})", source_chunk_x, source_chunk_z); - let new_chunk_x = source_chunk_x; - let new_chunk_z = source_chunk_z; - let has_sections = level.contains_key("Sections"); - let uses_modern_content = level.contains_key("Status") || level.contains_key("block_states"); - eprintln!("[convert] has_sections: {}, uses_modern_content: {}", has_sections, uses_modern_content); - let (blocks, data, _sky_light, _block_light) = if has_sections { - let shift = context.global_modern_section_shift; - modern_chunk_writer::flatten_anvil_sections(&level, shift, &mut context) - } else { - let blocks_arr = NbtHelper::get_byte_array_or_default(&level, "Blocks", CHUNK_BLOCKS); - let data_arr = NbtHelper::get_byte_array_or_default(&level, "Data", CHUNK_NIBBLES); - let sky_arr = NbtHelper::get_byte_array_or_default(&level, "SkyLight", CHUNK_NIBBLES); - let block_arr = NbtHelper::get_byte_array_or_default(&level, "BlockLight", CHUNK_NIBBLES); - let mut bl = [0u8; CHUNK_BLOCKS]; - let mut da = [0u8; CHUNK_NIBBLES]; - let mut sk = [0u8; CHUNK_NIBBLES]; - let mut blk = [0u8; CHUNK_NIBBLES]; - bl[..blocks_arr.len().min(CHUNK_BLOCKS)].copy_from_slice(&blocks_arr[..blocks_arr.len().min(CHUNK_BLOCKS)]); - da[..data_arr.len().min(CHUNK_NIBBLES)].copy_from_slice(&data_arr[..data_arr.len().min(CHUNK_NIBBLES)]); - sk[..sky_arr.len().min(CHUNK_NIBBLES)].copy_from_slice(&sky_arr[..sky_arr.len().min(CHUNK_NIBBLES)]); - blk[..block_arr.len().min(CHUNK_NIBBLES)].copy_from_slice(&block_arr[..block_arr.len().min(CHUNK_NIBBLES)]); - (bl, da, sk, blk) - }; - - let height_map = NbtHelper::get_byte_array_or_default(&level, "HeightMap", HEIGHTMAP_SIZE); - let biomes_data = NbtHelper::get_byte_array_or_default(&level, "Biomes", BIOMES_SIZE); - let mut biomes_arr = [0u8; BIOMES_SIZE]; - let len = biomes_data.len().min(BIOMES_SIZE); - biomes_arr[..len].copy_from_slice(&biomes_data[..len]); - if uses_modern_content { - biomes_arr.fill(1); - } - - let mut hm = [0u8; HEIGHTMAP_SIZE]; - let len = height_map.len().min(HEIGHTMAP_SIZE); - hm[..len].copy_from_slice(&height_map[..len]); - let last_update = NbtHelper::get_long(&level, "LastUpdate").unwrap_or(0); - let inhabited_time = NbtHelper::get_long(&level, "InhabitedTime").unwrap_or(0); - let entities = if context.preserve_dynamic_chunk_data && !uses_modern_content { - NbtHelper::get_list(&level, "Entities").unwrap_or_default() - } else { - Vec::new() - }; - - let tile_entities = if context.preserve_dynamic_chunk_data { - NbtHelper::get_list(&level, "TileEntities").unwrap_or_default() - } else { - Vec::new() - }; - - eprintln!("[convert] encoding chunk NBT..."); - let chunk_payload = match lce_chunk_payload::encode_legacy_nbt( - new_chunk_x, new_chunk_z, - &blocks, &data, &[0u8; 32768], &[0u8; 32768], - &hm, &biomes_arr, - last_update, inhabited_time, - entities, tile_entities, None, - ) { - Ok(p) => { - eprintln!("[convert] encoded {} bytes", p.len()); - p - } - Err(e) => { - eprintln!("[convert] ERROR encoding chunk: {}", e); - continue; - } - }; - - if let Err(e) = dim_region.add_chunk(new_chunk_x, new_chunk_z, &chunk_payload) { - eprintln!("[convert] ERROR adding chunk to region: {}", e); - continue; - } - total_chunks += 1; - } - } - - eprintln!("[convert] building region file for {:?}...", dim_dir); - let region_bytes = match dim_region.build() { - Ok(b) => { - eprintln!("[convert] region built, {} bytes", b.len()); - b - } - Err(e) => { - eprintln!("[convert] ERROR building region: {}", e); - continue; - } - }; - - let region_key = if dim_dir == "region" { - "region/r.0.0.mcr".to_string() - } else { - format!("{}/r.0.0.mcr", dim_dir) - }; - - regions.insert(region_key, region_bytes); - } - - eprintln!("[convert] writing saveData.ms with {} chunks", total_chunks); - let ms_data = match save_data_container::write_save_data_ms(&lce_level_dat, ®ions) { - Ok(d) => { - eprintln!("[convert] saveData.ms content built, {} bytes", d.len()); - d - } - Err(e) => { - eprintln!("[convert] ERROR building saveData.ms: {}", e); - return Err(e); - } - }; - - eprintln!("[convert] writing to {:?}", output_path); - if let Err(e) = fs::write(output_path, &ms_data) { - eprintln!("[convert] ERROR writing output file: {}", e); - return Err(ConversionError::Io(e)); - } - eprintln!("[convert] done!"); - - Ok(ConversionResult { - success: true, - message: format!("Successfully converted {} chunks", total_chunks), - chunk_count: total_chunks, - unknown_blocks: context.unknown_blocks, - }) -} diff --git a/src-tauri/src/console2lce/error.rs b/src-tauri/src/console2lce/error.rs deleted file mode 100644 index fbe1bed..0000000 --- a/src-tauri/src/console2lce/error.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::fmt; -use std::io; -#[derive(Debug)] -pub enum ConversionError { - Io(io::Error), - InvalidFormat(String), - UnsupportedVersion(String), - NbtError(String), - MissingData(String), - DecompressionFailed(String), - CompressionFailed(String), -} - -impl fmt::Display for ConversionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ConversionError::Io(e) => write!(f, "IO error: {}", e), - ConversionError::InvalidFormat(s) => write!(f, "Invalid format: {}", s), - ConversionError::UnsupportedVersion(s) => write!(f, "Unsupported version: {}", s), - ConversionError::NbtError(s) => write!(f, "NBT error: {}", s), - ConversionError::MissingData(s) => write!(f, "Missing data: {}", s), - ConversionError::DecompressionFailed(s) => write!(f, "Decompression failed: {}", s), - ConversionError::CompressionFailed(s) => write!(f, "Compression failed: {}", s), - } - } -} - -impl std::error::Error for ConversionError {} -impl From for ConversionError { - fn from(e: io::Error) -> Self { - ConversionError::Io(e) - } -} diff --git a/src-tauri/src/console2lce/lce_chunk_payload.rs b/src-tauri/src/console2lce/lce_chunk_payload.rs deleted file mode 100644 index ea97757..0000000 --- a/src-tauri/src/console2lce/lce_chunk_payload.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::collections::HashMap; -use std::io::Write; -use flate2::write::ZlibEncoder; -use flate2::Compression as FlateCompression; -use crate::console2lce::error::ConversionError; -use crate::console2lce::nbt_util::{NbtTag, write_nbt_le}; -pub fn encode_legacy_nbt( - x_pos: i32, - z_pos: i32, - blocks: &[u8; 65536], - data: &[u8; 32768], - sky_light: &[u8; 32768], - _block_light: &[u8; 32768], - height_map: &[u8; 256], - biomes: &[u8; 256], - last_update: i64, - inhabited_time: i64, - entities: Vec, - tile_entities: Vec, - tile_ticks: Option>, -) -> Result, ConversionError> { - let mut level_map = HashMap::new(); - level_map.insert("xPos".to_string(), NbtTag::Int(x_pos)); - level_map.insert("zPos".to_string(), NbtTag::Int(z_pos)); - level_map.insert("LastUpdate".to_string(), NbtTag::Long(last_update)); - level_map.insert("InhabitedTime".to_string(), NbtTag::Long(inhabited_time)); - level_map.insert("Blocks".to_string(), NbtTag::ByteArray(blocks.to_vec())); - level_map.insert("Data".to_string(), NbtTag::ByteArray(data.to_vec())); - level_map.insert("SkyLight".to_string(), NbtTag::ByteArray(sky_light.to_vec())); - level_map.insert("BlockLight".to_string(), NbtTag::ByteArray(_block_light.to_vec())); - level_map.insert("HeightMap".to_string(), NbtTag::ByteArray(height_map.to_vec())); - level_map.insert("TerrainPopulatedFlags".to_string(), NbtTag::Short(0)); - level_map.insert("Biomes".to_string(), NbtTag::ByteArray(biomes.to_vec())); - level_map.insert("Entities".to_string(), NbtTag::List(entities, 10)); - level_map.insert("TileEntities".to_string(), NbtTag::List(tile_entities, 10)); - if let Some(ticks) = tile_ticks { - level_map.insert("TileTicks".to_string(), NbtTag::List(ticks, 10)); - } - - let root = NbtTag::Compound(level_map); - let nbt_data = write_nbt_le(&root)?; - let rle_compressed = rle_compress(&nbt_data); - let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default()); - encoder.write_all(&rle_compressed)?; - let zlib_data = encoder.finish()?; - Ok(zlib_data) -} - -pub fn encode_compressed_storage( - x_pos: i32, - z_pos: i32, - blocks: &[u8; 65536], - data: &[u8; 32768], - sky_light: &[u8; 32768], - _block_light: &[u8; 32768], - height_map: &[u8; 256], - biomes: &[u8; 256], - last_update: i64, -) -> Result, ConversionError> { - let sections = build_compressed_tile_storage_sections(blocks, data, sky_light); - let mut level = HashMap::new(); - level.insert("xPos".to_string(), NbtTag::Int(x_pos)); - level.insert("zPos".to_string(), NbtTag::Int(z_pos)); - level.insert("LastUpdate".to_string(), NbtTag::Long(last_update)); - level.insert("Sections".to_string(), NbtTag::List(sections, 10)); - level.insert("HeightMap".to_string(), NbtTag::ByteArray(height_map.to_vec())); - level.insert("Biomes".to_string(), NbtTag::ByteArray(biomes.to_vec())); - let root = NbtTag::Compound(level); - let nbt_data = write_nbt_le(&root)?; - let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default()); - encoder.write_all(&nbt_data)?; - let zlib_data = encoder.finish()?; - Ok(zlib_data) -} - -fn build_compressed_tile_storage_sections( - blocks: &[u8; 65536], - data: &[u8; 32768], - sky_light: &[u8; 32768], -) -> Vec { - let mut sections = Vec::new(); - for section_y in 0..8 { - let half_chunk_blocks = extract_half_chunk(blocks, section_y, 65536); - let half_chunk_data = extract_half_chunk(data, section_y, 32768); - let half_chunk_sky = extract_half_chunk(sky_light, section_y, 32768); - let storage = build_single_compressed_storage(&half_chunk_blocks, &half_chunk_data, &half_chunk_sky); - let mut section = HashMap::new(); - section.insert("Y".to_string(), NbtTag::Byte(section_y as i8)); - section.insert("Blocks".to_string(), NbtTag::ByteArray(storage)); - sections.push(NbtTag::Compound(section)); - } - - sections -} - -fn extract_half_chunk(full: &[u8], section_y: usize, full_size: usize) -> Vec { - let section_size = 16384; - let mut result = vec![0u8; section_size]; - let base_y = section_y * 16; - for x in 0..16 { - for z in 0..16 { - for y in 0..16 { - let src_y = base_y + y; - if src_y >= 128 { - continue; - } - let src_idx = ((x * 16) + z) * 256 + src_y; - if src_idx < full_size { - let dst_idx = (y * 16 + z) * 16 + x; - if dst_idx < section_size { - result[dst_idx] = full[src_idx]; - } - } - } - } - } - - result -} - -fn build_single_compressed_storage(blocks: &[u8], data: &[u8], _sky_light: &[u8]) -> Vec { - let mut storage = Vec::new(); - let num_indices = 1024; - let mut index_table = vec![0u16; num_indices]; - let mut payload = Vec::new(); - for idx in 0..num_indices { - let base = idx * 16; - let mut uses_high_data = false; - let mut max_id: u8 = 0; - let mut max_data: u8 = 0; - for j in 0..16 { - let block_pos = base + j; - if block_pos >= blocks.len() { - break; - } - let b = blocks[block_pos]; - let d = if block_pos < data.len() { - get_nibble(data, block_pos) - } else { - 0 - }; - if b > max_id { max_id = b; } - if d > max_data { max_data = d; } - if d > 0 { uses_high_data = true; } - } - - if max_id == 0 && max_data == 0 { - index_table[idx] = 0; - continue; - } - - if max_id <= 15 && !uses_high_data { - index_table[idx] = (1 << 13) | (payload.len() as u16); - for j in 0..16 { - let block_pos = base + j; - if block_pos < blocks.len() { - let b = blocks[block_pos]; - if j % 2 == 0 { - payload.push(b << 4); - } else { - let last = payload.last_mut().unwrap(); - *last |= b & 0x0F; - } - } - } - continue; - } - - if max_data == 0 { - index_table[idx] = (2 << 13) | (payload.len() as u16); - for j in 0..16 { - let block_pos = base + j; - if block_pos < blocks.len() { - payload.push(blocks[block_pos]); - } - } - continue; - } - - if max_id > 0 || max_data > 0 { - index_table[idx] = (3 << 13) | (payload.len() as u16); - for j in 0..16 { - let block_pos = base + j; - if block_pos < blocks.len() { - payload.push(blocks[block_pos]); - if block_pos < data.len() { - payload.push(get_nibble(data, block_pos)); - } else { - payload.push(0); - } - } - } - } - } - - for &entry in &index_table { - storage.extend_from_slice(&entry.to_be_bytes()); - } - storage.extend_from_slice(&payload); - storage -} - -fn get_nibble(arr: &[u8], index: usize) -> u8 { - let b = arr[index >> 1]; - if (index & 1) == 0 { b & 0x0F } else { (b >> 4) & 0x0F } -} - -pub fn rle_compress(data: &[u8]) -> Vec { - let mut output = Vec::new(); - let mut pos = 0; - while pos < data.len() { - let run_start = pos; - let mut run_len = 0u8; - while run_start + (run_len as usize) < data.len() && run_len < 255 { - if data[run_start] == data[run_start + (run_len as usize)] { - run_len += 1; - } else { - break; - } - } - - if run_len >= 4 { - output.push(0xFF); - output.push(run_len); - output.push(data[run_start]); - pos = run_start + run_len as usize; - } else { - output.push(data[pos]); - pos += 1; - } - } - - output -} diff --git a/src-tauri/src/console2lce/lce_region.rs b/src-tauri/src/console2lce/lce_region.rs deleted file mode 100644 index e99c429..0000000 --- a/src-tauri/src/console2lce/lce_region.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::BTreeMap; -use crate::console2lce::error::ConversionError; -const SECTOR_SIZE: usize = 4096; -const HEADER_SECTORS: usize = 2; -pub struct LceRegionFile { - sectors: Vec, - offsets: BTreeMap<(i32, i32), (u32, u32, u32)>, - next_sector: u32, -} - -impl LceRegionFile { - pub fn new() -> Self { - let header_size = HEADER_SECTORS * SECTOR_SIZE; - LceRegionFile { - sectors: vec![0u8; header_size], - offsets: BTreeMap::new(), - next_sector: HEADER_SECTORS as u32, - } - } - - pub fn add_chunk(&mut self, chunk_x: i32, chunk_z: i32, data: &[u8]) -> Result<(), ConversionError> { - if data.len() > 1024 * 1024 { - return Err(ConversionError::InvalidFormat(format!( - "Chunk ({}, {}) data too large: {} bytes", chunk_x, chunk_z, data.len() - ))); - } - - let total_size = 5 + data.len(); - let sectors_needed = ((total_size + SECTOR_SIZE - 1) / SECTOR_SIZE) as u32; - let sector_start = self.next_sector; - let padding = sectors_needed as usize * SECTOR_SIZE - total_size; - let length_be = (data.len() as u32).to_be_bytes(); - self.sectors.extend_from_slice(&length_be); - self.sectors.push(2); - self.sectors.extend_from_slice(data); - for _ in 0..padding { - self.sectors.push(0); - } - - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as u32; - - self.offsets.insert((chunk_x, chunk_z), (sector_start, sectors_needed, timestamp)); - self.next_sector = sector_start + sectors_needed; - Ok(()) - } - - pub fn build(mut self) -> Result, ConversionError> { - for ((chunk_x, chunk_z), (sector_start, sector_count, timestamp)) in &self.offsets { - let local_x = chunk_x.rem_euclid(32) as usize; - let local_z = chunk_z.rem_euclid(32) as usize; - let index = local_z * 32 + local_x; - let offset_val = (sector_start << 8) | (sector_count & 0xFF); - let offset_bytes = offset_val.to_be_bytes(); - let ts_bytes = timestamp.to_be_bytes(); - let off_pos = index * 4; - let ts_pos = 4096 + index * 4; - if off_pos + 4 <= self.sectors.len() { - self.sectors[off_pos..off_pos + 4].copy_from_slice(&offset_bytes); - } - if ts_pos + 4 <= self.sectors.len() { - self.sectors[ts_pos..ts_pos + 4].copy_from_slice(&ts_bytes); - } - } - - Ok(self.sectors) - } -} diff --git a/src-tauri/src/console2lce/level_dat.rs b/src-tauri/src/console2lce/level_dat.rs deleted file mode 100644 index 69ce001..0000000 --- a/src-tauri/src/console2lce/level_dat.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::collections::HashMap; -use crate::console2lce::error::ConversionError; -use crate::console2lce::nbt_util::{NbtTag, NbtHelper, write_zlibbed_nbt}; -use crate::console2lce::world_profile::{get_profile, world_center_offset}; -use crate::console2lce::models::ChunkConversionContext; -pub fn convert_level_dat( - source_data: &[u8], - profile_name: &str, - game_version: &str, - _context: &mut ChunkConversionContext, -) -> Result, ConversionError> { - let (_name, root) = crate::console2lce::nbt_util::read_gzipped_nbt(source_data)?; - let data = match root.as_compound() { - Some(m) => m.clone(), - None => return Err(ConversionError::InvalidFormat("level.dat root is not a compound".to_string())), - }; - - let data_tag = NbtHelper::get_compound(&data, "Data") - .unwrap_or_else(|| data.clone()); - - let profile = get_profile(profile_name).ok_or_else(|| { - ConversionError::InvalidFormat(format!("Unknown profile: {}", profile_name)) - })?; - - let center_offset = world_center_offset(profile.xz_size); - let spawn_x = NbtHelper::get_int(&data_tag, "SpawnX").unwrap_or(0) as i32; - let spawn_z = NbtHelper::get_int(&data_tag, "SpawnZ").unwrap_or(0) as i32; - let new_spawn_x = clamp_to_world(spawn_x, center_offset, profile.xz_size as i32); - let new_spawn_z = clamp_to_world(spawn_z, center_offset, profile.xz_size as i32); - let game_type = NbtHelper::get_int(&data_tag, "GameType").unwrap_or(0); - let rain_time = NbtHelper::get_int(&data_tag, "rainTime").unwrap_or(0); - let thunder_time = NbtHelper::get_int(&data_tag, "thunderTime").unwrap_or(0); - let raining = NbtHelper::get_byte(&data_tag, "raining").unwrap_or(0) != 0; - let thundering = NbtHelper::get_byte(&data_tag, "thundering").unwrap_or(0) != 0; - let seed = NbtHelper::get_long(&data_tag, "RandomSeed").unwrap_or(0); - let level_name = NbtHelper::get_string(&data_tag, "LevelName").unwrap_or_else(|| "Imported World".to_string()); - let day_time = NbtHelper::get_int(&data_tag, "DayTime").unwrap_or(0); - let allow_commands = NbtHelper::get_byte(&data_tag, "allowCommands").unwrap_or(0) != 0; - let difficulty = NbtHelper::get_byte(&data_tag, "Difficulty").unwrap_or(2); - let difficulty_locked = NbtHelper::get_byte(&data_tag, "DifficultyLocked").unwrap_or(0) != 0; - let hardcore = NbtHelper::get_byte(&data_tag, "hardcore").unwrap_or(0) != 0; - let map_features = NbtHelper::get_byte(&data_tag, "MapFeatures").unwrap_or(1) != 0; - let generator_name = NbtHelper::get_string(&data_tag, "generatorName").unwrap_or_else(|| "default".to_string()); - let generator_version = NbtHelper::get_int(&data_tag, "generatorVersion").unwrap_or(1); - let generator_options = NbtHelper::get_string(&data_tag, "generatorOptions").unwrap_or_default(); - let initialized = NbtHelper::get_byte(&data_tag, "initialized").unwrap_or(1) != 0; - let clear_weather = rained_since_last_thunder_cleanup(&data_tag); - let mut game_rules = HashMap::new(); - if let Some(rules) = NbtHelper::get_compound(&data_tag, "GameRules") { - for (k, v) in &rules { - if let Some(s) = v.get_string() { - game_rules.insert(k.clone(), NbtTag::String(s.to_string())); - } - } - } - - let mut lce_data = HashMap::new(); - lce_data.insert("allowCommands".to_string(), NbtTag::Byte(if allow_commands { 1 } else { 0 })); - lce_data.insert("clearedWeather".to_string(), NbtTag::Byte(if clear_weather { 1 } else { 0 })); - lce_data.insert("clearWeatherTime".to_string(), NbtTag::Int(0)); - lce_data.insert("currentSaveVersion".to_string(), NbtTag::Int(9)); - lce_data.insert("DayTime".to_string(), NbtTag::Int(day_time)); - lce_data.insert("Difficulty".to_string(), NbtTag::Byte(difficulty)); - lce_data.insert("DifficultyLocked".to_string(), NbtTag::Byte(if difficulty_locked { 1 } else { 0 })); - lce_data.insert("GameType".to_string(), NbtTag::Int(game_type)); - lce_data.insert("generatorName".to_string(), NbtTag::String(generator_name)); - lce_data.insert("generatorVersion".to_string(), NbtTag::Int(generator_version)); - lce_data.insert("generatorOptions".to_string(), NbtTag::String(generator_options)); - lce_data.insert("hardcore".to_string(), NbtTag::Byte(if hardcore { 1 } else { 0 })); - lce_data.insert("HellScale".to_string(), NbtTag::Int(profile.hell_scale as i32)); - lce_data.insert("initialized".to_string(), NbtTag::Byte(if initialized { 1 } else { 0 })); - lce_data.insert("LevelName".to_string(), NbtTag::String(level_name)); - lce_data.insert("MapFeatures".to_string(), NbtTag::Byte(if map_features { 1 } else { 0 })); - lce_data.insert("originalSaveVersion".to_string(), NbtTag::Int(7)); - lce_data.insert("rainTime".to_string(), NbtTag::Int(rain_time)); - lce_data.insert("RandomSeed".to_string(), NbtTag::Long(seed)); - lce_data.insert("SpawnX".to_string(), NbtTag::Int(new_spawn_x)); - lce_data.insert("SpawnZ".to_string(), NbtTag::Int(new_spawn_z)); - lce_data.insert("thunderTime".to_string(), NbtTag::Int(thunder_time)); - lce_data.insert("XzSize".to_string(), NbtTag::Int(profile.xz_size as i32)); - lce_data.insert("raining".to_string(), NbtTag::Byte(if raining { 1 } else { 0 })); - lce_data.insert("thundering".to_string(), NbtTag::Byte(if thundering { 1 } else { 0 })); - if !game_rules.is_empty() { - lce_data.insert("GameRules".to_string(), NbtTag::Compound(game_rules)); - } - - let version_info = build_version_info(game_version); - lce_data.insert("Version".to_string(), NbtTag::Compound(version_info)); - let root = NbtTag::Compound({ - let mut m = HashMap::new(); - m.insert("Data".to_string(), NbtTag::Compound(lce_data)); - m - }); - - write_zlibbed_nbt("", &root) -} - -fn clamp_to_world(value: i32, center: i32, xz_size: i32) -> i32 { - let half = xz_size * 8; - value.clamp(center - half, center + half - 1) -} - -fn rained_since_last_thunder_cleanup(data: &HashMap) -> bool { - let rain_time = NbtHelper::get_int(data, "rainTime").unwrap_or(0); - let thunder_time = NbtHelper::get_int(data, "thunderTime").unwrap_or(0); - rain_time > 0 && thunder_time == 0 -} - -fn build_version_info(game_version: &str) -> HashMap { - let mut version = HashMap::new(); - version.insert("Id".to_string(), NbtTag::Int(19133)); - version.insert("Name".to_string(), NbtTag::String(game_version.to_string())); - version.insert("Snapshot".to_string(), NbtTag::Byte(0)); - version -} diff --git a/src-tauri/src/console2lce/mod.rs b/src-tauri/src/console2lce/mod.rs deleted file mode 100644 index 9b4ccc2..0000000 --- a/src-tauri/src/console2lce/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub mod error; -pub mod models; -pub mod world_profile; -pub mod nbt_util; -pub mod stfs; -pub mod archive; -pub mod region; -pub mod chunk; -pub mod block_mapping; -pub mod modern_chunk_writer; -pub mod lce_chunk_payload; -pub mod lce_region; -pub mod level_dat; -pub mod save_data_container; -pub mod convert; -pub use error::ConversionError; -pub use models::{ConversionOptions, ConversionResult, ChunkConversionContext}; -pub use world_profile::WorldProfile; -pub use convert::{convert_java_world_to_lce, convert_xbox360_save_to_lce}; diff --git a/src-tauri/src/console2lce/models.rs b/src-tauri/src/console2lce/models.rs deleted file mode 100644 index 83f27ec..0000000 --- a/src-tauri/src/console2lce/models.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::collections::HashMap; -#[derive(Debug, Clone, Copy)] -pub struct LegacyBlockState { - pub id: u8, - pub data: u8, -} - -impl LegacyBlockState { - pub fn new(id: u8, data: u8) -> Self { - LegacyBlockState { id, data } - } -} - -#[derive(Debug, Clone)] -pub struct ChunkConversionContext { - pub preserve_dynamic_chunk_data: bool, - pub global_modern_section_shift: Option, - pub unknown_blocks: Vec, -} - -impl ChunkConversionContext { - pub fn new() -> Self { - ChunkConversionContext { - preserve_dynamic_chunk_data: false, - global_modern_section_shift: None, - unknown_blocks: Vec::new(), - } - } - - pub fn record_unknown_modern_block(&mut self, name: &str) { - if !self.unknown_blocks.iter().any(|s| s == name) { - self.unknown_blocks.push(name.to_string()); - } - } -} - -#[derive(Debug, Clone)] -pub struct ConversionOptions { - pub profile: String, - pub target_version: String, - pub game_version: String, - pub preserve_entities: bool, - pub preview: bool, -} - -impl Default for ConversionOptions { - fn default() -> Self { - ConversionOptions { - profile: "large".to_string(), - target_version: "TU19".to_string(), - game_version: "1.13".to_string(), - preserve_entities: false, - preview: false, - } - } -} - -#[derive(Debug, Clone)] -pub struct ConversionResult { - pub success: bool, - pub message: String, - pub chunk_count: usize, - pub unknown_blocks: Vec, -} - -#[derive(Debug, Clone)] -pub struct PreparedJavaWorld { - pub level_dat: HashMap>, - pub region_files: Vec, - pub dimension_dirs: Vec, -} - -#[derive(Debug, Clone)] -pub struct JavaRegionFile { - pub path: String, - pub dimension: String, - pub data: Vec, - pub chunk_count: usize, -} - -#[derive(Debug, Clone)] -pub struct ArchiveEntry { - pub name: String, - pub offset: u64, - pub length: u32, - pub timestamp: u64, -} - -#[derive(Debug, Clone)] -pub struct StfsPackageDescriptor { - pub magic: u32, - pub content_type: u32, - pub meta_data_size: u32, - pub total_block_count: u32, - pub block_allocation_table_size: u32, -} - -#[derive(Debug, Clone)] -pub struct SavegameEnvelope { - pub data: Vec, - pub decompressed_size: u32, - pub compression_type: String, -} - -pub struct DecodedSection { - pub section_y: i32, - pub blocks: [u8; 4096], - pub data: [u8; 2048], - pub sky_light: Option<[u8; 2048]>, - pub block_light: Option<[u8; 2048]>, - pub non_air_count: i32, -} - -pub const CHUNK_BLOCKS: usize = 65536; -pub const CHUNK_NIBBLES: usize = 32768; -pub const HEIGHTMAP_SIZE: usize = 256; -pub const BIOMES_SIZE: usize = 256; -pub struct SaveDataContainerHeader { - pub magic: [u8; 6], - pub version: u32, - pub file_count: u32, -} - -pub struct SaveDataContainerFileEntry { - pub offset: u64, - pub length: u64, - pub timestamp: u64, - pub name: String, -} diff --git a/src-tauri/src/console2lce/modern_chunk_writer.rs b/src-tauri/src/console2lce/modern_chunk_writer.rs deleted file mode 100644 index 4508df1..0000000 --- a/src-tauri/src/console2lce/modern_chunk_writer.rs +++ /dev/null @@ -1,737 +0,0 @@ -use std::collections::HashMap; -use crate::console2lce::models::{LegacyBlockState, ChunkConversionContext, CHUNK_BLOCKS, CHUNK_NIBBLES}; -use crate::console2lce::nbt_util::{NbtTag, NbtHelper}; -use crate::console2lce::block_mapping; -pub fn map_modern_block_state(name: &str, properties: &HashMap, context: &mut ChunkConversionContext) -> LegacyBlockState { - let name = name.strip_prefix("minecraft:").unwrap_or(name); - if let Some(result) = try_map_fluid_block(name, properties) { - return result; - } - if let Some(result) = try_map_slab_block(name, properties) { - return result; - } - if let Some(result) = try_map_directional_block(name, properties) { - return result; - } - if let Some(result) = try_map_flattened_colored_block(name) { - return result; - } - if let Some(legacy) = block_mapping::MODERN_DIRECT_MAP.get(name) { - return *legacy; - } - if let Some(result) = try_map_colored_block(name, properties) { - return result; - } - if let Some(result) = try_map_wood_block(name, properties) { - return result; - } - if let Some(result) = try_map_variant_block(name, properties) { - return result; - } - - context.record_unknown_modern_block(name); - LegacyBlockState::new(0, 0) -} - -fn get_prop<'a>(properties: &'a HashMap, key: &str) -> &'a str { - properties.get(key).map(|s| s.as_str()).unwrap_or("") -} - -fn get_bool_prop(properties: &HashMap, key: &str) -> bool { - get_prop(properties, key) == "true" -} - -fn get_int_prop(properties: &HashMap, key: &str, default: i32) -> i32 { - get_prop(properties, key).parse::().unwrap_or(default) -} - -fn try_map_fluid_block(name: &str, properties: &HashMap) -> Option { - if name != "water" && name != "lava" { - return None; - } - let level = get_int_prop(properties, "level", 0).clamp(0, 15); - let is_source = level == 0; - if name == "water" { - Some(LegacyBlockState::new(if is_source { 9 } else { 8 }, level as u8)) - } else { - Some(LegacyBlockState::new(if is_source { 11 } else { 10 }, level as u8)) - } -} - -fn try_map_slab_block(name: &str, properties: &HashMap) -> Option { - if !name.ends_with("_slab") { - return None; - } - let slab_type = get_prop(properties, "type"); - let is_top = slab_type == "top"; - let is_double = slab_type == "double"; - if let Some(variant) = get_wood_slab_variant(name) { - if is_double { - return Some(LegacyBlockState::new(125, variant)); - } - return Some(LegacyBlockState::new(126, variant | if is_top { 8 } else { 0 })); - } - - if let Some(variant) = get_stone_slab_variant(name) { - if is_double { - return Some(LegacyBlockState::new(43, variant)); - } - return Some(LegacyBlockState::new(44, variant | if is_top { 8 } else { 0 })); - } - - if is_double { - Some(LegacyBlockState::new(43, 0)) - } else { - Some(LegacyBlockState::new(44, if is_top { 8 } else { 0 })) - } -} - -fn get_wood_slab_variant(name: &str) -> Option { - match name { - "oak_slab" => Some(0), - "spruce_slab" => Some(1), - "birch_slab" => Some(2), - "jungle_slab" => Some(3), - "acacia_slab" => Some(4), - "dark_oak_slab" => Some(5), - _ => None, - } -} - -fn get_stone_slab_variant(name: &str) -> Option { - match name { - "stone_slab" | "smooth_stone_slab" | "andesite_slab" | "polished_andesite_slab" - | "diorite_slab" | "polished_diorite_slab" | "granite_slab" | "polished_granite_slab" => Some(0), - "sandstone_slab" | "smooth_sandstone_slab" | "cut_sandstone_slab" - | "red_sandstone_slab" | "smooth_red_sandstone_slab" | "cut_red_sandstone_slab" => Some(1), - "cobblestone_slab" | "mossy_cobblestone_slab" => Some(3), - "brick_slab" => Some(4), - "stone_brick_slab" | "mossy_stone_brick_slab" => Some(5), - "nether_brick_slab" | "red_nether_brick_slab" => Some(6), - "quartz_slab" | "smooth_quartz_slab" | "purpur_slab" - | "prismarine_slab" | "prismarine_brick_slab" | "dark_prismarine_slab" => Some(7), - _ => None, - } -} - -fn try_map_directional_block(name: &str, properties: &HashMap) -> Option { - match name { - "ladder" => { - let data = map_ladder_facing(get_prop(properties, "facing")); - Some(LegacyBlockState::new(65, data)) - } - "vine" => { - let data = map_vine_faces(properties); - Some(LegacyBlockState::new(106, data)) - } - "lever" => { - let mut data = map_lever_data(get_prop(properties, "face"), get_prop(properties, "facing")); - if get_bool_prop(properties, "powered") { data |= 8; } - Some(LegacyBlockState::new(69, data)) - } - _ => { - if name.ends_with("_button") { - let id = if is_wood_family(name) { 143 } else { 77 }; - let mut data = map_button_facing(get_prop(properties, "facing")); - if get_bool_prop(properties, "powered") { data |= 8; } - return Some(LegacyBlockState::new(id, data)); - } - if name.ends_with("_fence_gate") { - let mut data = map_fence_gate_facing(get_prop(properties, "facing")); - if get_bool_prop(properties, "open") { data |= 4; } - return Some(LegacyBlockState::new(107, data)); - } - if name.ends_with("_pressure_plate") { - let id = match name { - "light_weighted_pressure_plate" => 147, - "heavy_weighted_pressure_plate" => 148, - n if is_wood_family(n) => 72, - _ => 70, - }; - let powered = get_bool_prop(properties, "powered") || get_int_prop(properties, "power", 0) > 0; - return Some(LegacyBlockState::new(id, if powered { 1 } else { 0 })); - } - if name.ends_with("_door") || name == "iron_door" { - let id = if name == "iron_door" { 71 } else { 64 }; - let half = get_prop(properties, "half"); - if half == "upper" { - let mut data: u8 = 8; - if get_prop(properties, "hinge") == "right" { data |= 1; } - if get_bool_prop(properties, "powered") { data |= 2; } - return Some(LegacyBlockState::new(id, data)); - } - let mut data = map_door_facing(get_prop(properties, "facing")); - if get_bool_prop(properties, "open") { data |= 4; } - return Some(LegacyBlockState::new(id, data)); - } - if let Some(stairs_id) = get_stairs_id(name) { - let mut data = map_stairs_facing(get_prop(properties, "facing")); - if get_prop(properties, "half") == "top" { data |= 4; } - return Some(LegacyBlockState::new(stairs_id, data)); - } - if name.ends_with("trapdoor") { - let mut data = map_trapdoor_facing(get_prop(properties, "facing")); - if get_bool_prop(properties, "open") { data |= 4; } - if get_prop(properties, "half") == "top" { data |= 8; } - return Some(LegacyBlockState::new(96, data)); - } - if name == "dispenser" || name == "dropper" { - let id = if name == "dropper" { 158 } else { 23 }; - let mut data = map_facing_data(get_prop(properties, "facing")); - if get_bool_prop(properties, "triggered") { data |= 8; } - return Some(LegacyBlockState::new(id, data)); - } - if name == "piston" || name == "sticky_piston" { - let id = if name == "sticky_piston" { 29 } else { 33 }; - let mut data = map_facing_data(get_prop(properties, "facing")); - if get_bool_prop(properties, "extended") { data |= 8; } - return Some(LegacyBlockState::new(id, data)); - } - if name == "piston_head" { - let mut data = map_facing_data(get_prop(properties, "facing")); - if get_prop(properties, "type") == "sticky" { data |= 8; } - return Some(LegacyBlockState::new(34, data)); - } - if name == "redstone_wire" { - let data = get_int_prop(properties, "power", 0).clamp(0, 15) as u8; - return Some(LegacyBlockState::new(55, data)); - } - if name == "repeater" { - let id = if get_bool_prop(properties, "powered") { 94 } else { 93 }; - let dir = map_repeater_direction(get_prop(properties, "facing")); - let delay = get_int_prop(properties, "delay", 1).clamp(1, 4); - let data = dir | ((delay - 1) as u8) << 2; - return Some(LegacyBlockState::new(id, data)); - } - if name == "comparator" { - let id = if get_bool_prop(properties, "powered") { 150 } else { 149 }; - let dir = map_repeater_direction(get_prop(properties, "facing")); - let mut data = dir; - if get_prop(properties, "mode") == "subtract" { data |= 4; } - return Some(LegacyBlockState::new(id, data)); - } - if name == "wall_torch" { - let data = map_wall_torch_facing(get_prop(properties, "facing")); - return Some(LegacyBlockState::new(50, data)); - } - if name == "redstone_wall_torch" { - let id = if get_bool_prop(properties, "lit") { 76 } else { 75 }; - let data = map_wall_torch_facing(get_prop(properties, "facing")); - return Some(LegacyBlockState::new(id, data)); - } - if name == "nether_wart" { - let data = get_int_prop(properties, "age", 0).clamp(0, 3) as u8; - return Some(LegacyBlockState::new(115, data)); - } - if name == "pumpkin_stem" || name == "attached_pumpkin_stem" { - let data = if name == "attached_pumpkin_stem" { 7 } else { get_int_prop(properties, "age", 0).clamp(0, 7) as u8 }; - return Some(LegacyBlockState::new(104, data)); - } - if name == "melon_stem" || name == "attached_melon_stem" { - let data = if name == "attached_melon_stem" { 7 } else { get_int_prop(properties, "age", 0).clamp(0, 7) as u8 }; - return Some(LegacyBlockState::new(105, data)); - } - if name == "cocoa" { - let age = get_int_prop(properties, "age", 0).clamp(0, 2) as u8; - let dir = map_repeater_direction(get_prop(properties, "facing")); - return Some(LegacyBlockState::new(127, (age << 2) | dir)); - } - if name == "hay_block" { - let data = match get_prop(properties, "axis") { - "x" => 4, - "z" => 8, - _ => 0, - }; - return Some(LegacyBlockState::new(170, data)); - } - if name == "quartz_pillar" { - let data = match get_prop(properties, "axis") { - "x" => 3, - "z" => 4, - _ => 2, - }; - return Some(LegacyBlockState::new(155, data)); - } - if name == "nether_portal" { - let data = if get_prop(properties, "axis") == "z" { 2 } else { 1 }; - return Some(LegacyBlockState::new(90, data)); - } - None - } - } -} - -fn is_wood_family(name: &str) -> bool { - let woods = ["oak", "spruce", "birch", "jungle", "acacia", "dark_oak"]; - woods.iter().any(|w| name.starts_with(w)) -} - -fn get_stairs_id(name: &str) -> Option { - match name { - "oak_stairs" | "spruce_stairs" | "birch_stairs" | "jungle_stairs" - | "acacia_stairs" | "dark_oak_stairs" => Some(53), - "cobblestone_stairs" | "mossy_cobblestone_stairs" | "stone_stairs" => Some(67), - "brick_stairs" => Some(108), - "stone_brick_stairs" | "mossy_stone_brick_stairs" => Some(109), - "nether_brick_stairs" => Some(114), - "sandstone_stairs" | "red_sandstone_stairs" => Some(128), - "quartz_stairs" | "purpur_stairs" => Some(156), - _ => None, - } -} - -fn map_stairs_facing(facing: &str) -> u8 { - match facing { - "east" => 0, "west" => 1, "south" => 2, "north" => 3, _ => 0, - } -} - -fn map_ladder_facing(facing: &str) -> u8 { - match facing { - "north" => 2, "south" => 3, "west" => 4, "east" => 5, _ => 2, - } -} - -fn map_button_facing(facing: &str) -> u8 { - match facing { - "east" => 1, "west" => 2, "south" => 3, "north" => 4, _ => 1, - } -} - -fn map_fence_gate_facing(facing: &str) -> u8 { - match facing { - "south" => 0, "west" => 1, "north" => 2, "east" => 3, _ => 0, - } -} - -fn map_vine_faces(properties: &HashMap) -> u8 { - let mut data = 0u8; - if get_bool_prop(properties, "south") { data |= 1; } - if get_bool_prop(properties, "west") { data |= 2; } - if get_bool_prop(properties, "north") { data |= 4; } - if get_bool_prop(properties, "east") { data |= 8; } - data -} - -fn map_lever_data(face: &str, facing: &str) -> u8 { - match face { - "floor" => if facing == "east" || facing == "west" { 6 } else { 5 }, - "ceiling" => if facing == "east" || facing == "west" { 7 } else { 0 }, - _ => map_button_facing(facing), - } -} - -fn map_door_facing(facing: &str) -> u8 { - match facing { - "east" => 0, "south" => 1, "west" => 2, "north" => 3, _ => 0, - } -} - -fn map_trapdoor_facing(facing: &str) -> u8 { - match facing { - "north" => 0, "south" => 1, "west" => 2, "east" => 3, _ => 0, - } -} - -fn map_facing_data(facing: &str) -> u8 { - match facing { - "down" => 0, "up" => 1, "north" => 2, "south" => 3, "west" => 4, "east" => 5, _ => 3, - } -} - -fn map_repeater_direction(facing: &str) -> u8 { - match facing { - "south" => 0, "west" => 1, "north" => 2, "east" => 3, _ => 0, - } -} - -fn map_wall_torch_facing(facing: &str) -> u8 { - match facing { - "east" => 1, "west" => 2, "south" => 3, "north" => 4, _ => 1, - } -} - -fn try_map_colored_block(name: &str, properties: &HashMap) -> Option { - let color = get_prop(properties, "color"); - let color_data = block_mapping::get_color_data(color); - - match name { - "wool" => Some(LegacyBlockState::new(35, color_data)), - "stained_glass" => Some(LegacyBlockState::new(95, color_data)), - "stained_glass_pane" => Some(LegacyBlockState::new(160, color_data)), - "terracotta" | "stained_hardened_clay" => Some(LegacyBlockState::new(159, color_data)), - "concrete" => Some(LegacyBlockState::new(172, color_data)), - "concrete_powder" => Some(LegacyBlockState::new(12, color_data)), - "glazed_terracotta" => Some(LegacyBlockState::new(159, color_data)), - _ => None, - } -} - -fn try_map_flattened_colored_block(name: &str) -> Option { - let (color_data, suffix) = split_color_prefix(name)?; - match suffix { - "wool" => Some(LegacyBlockState::new(35, color_data)), - "stained_glass" | "glass" => Some(LegacyBlockState::new(95, color_data)), - "stained_glass_pane" | "glass_pane" => Some(LegacyBlockState::new(160, color_data)), - "terracotta" | "stained_hardened_clay" => Some(LegacyBlockState::new(159, color_data)), - "concrete" => Some(LegacyBlockState::new(172, color_data)), - "concrete_powder" => Some(LegacyBlockState::new(12, color_data)), - "glazed_terracotta" => Some(LegacyBlockState::new(159, color_data)), - "carpet" => Some(LegacyBlockState::new(171, color_data)), - _ => None, - } -} - -fn split_color_prefix(name: &str) -> Option<(u8, &str)> { - for color_name in block_mapping::COLOR_NAMES { - if let Some(suffix) = name.strip_prefix(color_name).and_then(|s| s.strip_prefix('_')) { - let color_data = block_mapping::get_color_name(color_name); - return Some((color_data, suffix)); - } - } - None -} - -fn try_map_wood_block(name: &str, properties: &HashMap) -> Option { - let wood_type = get_prop(properties, "variant"); - let wood_type = if wood_type.is_empty() { - get_prefix_before_underscore(name) - } else { - wood_type.to_string() - }; - let data = block_mapping::get_wood_data(&wood_type); - - if name.ends_with("_planks") || name == "planks" { - return Some(LegacyBlockState::new(5, data)); - } - if name.ends_with("_sapling") || name == "sapling" { - return Some(LegacyBlockState::new(6, data)); - } - if name.ends_with("_log") || name == "log" { - return Some(LegacyBlockState::new(17, if data > 3 { 3 } else { data })); - } - if name.ends_with("_leaves") || name == "leaves" { - return Some(LegacyBlockState::new(18, if data > 3 { 3 } else { data })); - } - if name.ends_with("_stairs") && name.contains("wood") { - return Some(LegacyBlockState::new(53, 0)); - } - if name.ends_with("_door") { - return Some(LegacyBlockState::new(64, 0)); - } - if name.ends_with("_fence") { - return Some(LegacyBlockState::new(85, 0)); - } - if name.ends_with("_fence_gate") { - return Some(LegacyBlockState::new(107, 0)); - } - if name.ends_with("_pressure_plate") && is_wood_family(name) { - return Some(LegacyBlockState::new(72, 0)); - } - None -} - -fn get_prefix_before_underscore(name: &str) -> String { - if let Some(idx) = name.find('_') { - name[..idx].to_string() - } else { - String::new() - } -} - -fn try_map_variant_block(name: &str, properties: &HashMap) -> Option { - match name { - "redstone_lamp" => { - let lit = get_bool_prop(properties, "lit"); - Some(LegacyBlockState::new(if lit { 124 } else { 123 }, 0)) - } - "deepslate" | "polished_deepslate" | "tuff" | "calcite" | "dripstone_block" => Some(LegacyBlockState::new(1, 0)), - "cobbled_deepslate" => Some(LegacyBlockState::new(4, 0)), - "deepslate_bricks" | "deepslate_tiles" => Some(LegacyBlockState::new(98, 0)), - "cracked_deepslate_bricks" | "cracked_deepslate_tiles" => Some(LegacyBlockState::new(98, 2)), - "chiseled_deepslate" => Some(LegacyBlockState::new(98, 3)), - "deepslate_coal_ore" => Some(LegacyBlockState::new(16, 0)), - "deepslate_iron_ore" | "deepslate_copper_ore" => Some(LegacyBlockState::new(15, 0)), - "deepslate_gold_ore" => Some(LegacyBlockState::new(14, 0)), - "deepslate_redstone_ore" => Some(LegacyBlockState::new(73, 0)), - "deepslate_lapis_ore" => Some(LegacyBlockState::new(21, 0)), - "deepslate_diamond_ore" => Some(LegacyBlockState::new(56, 0)), - "deepslate_emerald_ore" => Some(LegacyBlockState::new(129, 0)), - "cobblestone_wall" => Some(LegacyBlockState::new(139, 0)), - "mossy_cobblestone_wall" => Some(LegacyBlockState::new(139, 1)), - "mossy_stone_bricks" => Some(LegacyBlockState::new(98, 1)), - "cracked_stone_bricks" => Some(LegacyBlockState::new(98, 2)), - "chiseled_stone_bricks" => Some(LegacyBlockState::new(98, 3)), - "smooth_sandstone" => Some(LegacyBlockState::new(24, 2)), - "chiseled_sandstone" | "cut_sandstone" => Some(LegacyBlockState::new(24, 1)), - "quartz_block" => Some(LegacyBlockState::new(155, 0)), - "chiseled_quartz_block" => Some(LegacyBlockState::new(155, 1)), - "prismarine" => Some(LegacyBlockState::new(168, 0)), - "prismarine_bricks" => Some(LegacyBlockState::new(168, 1)), - "dark_prismarine" => Some(LegacyBlockState::new(168, 2)), - "poppy" | "sunflower" | "lilac" | "peony" | "rose_bush" => Some(LegacyBlockState::new(38, 0)), - "blue_orchid" => Some(LegacyBlockState::new(38, 1)), - "allium" => Some(LegacyBlockState::new(38, 2)), - "azure_bluet" => Some(LegacyBlockState::new(38, 3)), - "red_tulip" => Some(LegacyBlockState::new(38, 4)), - "orange_tulip" => Some(LegacyBlockState::new(38, 5)), - "white_tulip" => Some(LegacyBlockState::new(38, 6)), - "pink_tulip" => Some(LegacyBlockState::new(38, 7)), - "oxeye_daisy" => Some(LegacyBlockState::new(38, 8)), - _ => None, - } -} - -pub fn get_nibble(arr: &[u8], index: usize) -> u8 { - let b = arr[index >> 1]; - if (index & 1) == 0 { b & 0x0F } else { (b >> 4) & 0x0F } -} - -pub fn set_nibble(arr: &mut [u8], index: usize, value: u8) { - let i = index >> 1; - let value = value & 0x0F; - if (index & 1) == 0 { - arr[i] = (arr[i] & 0xF0) | value; - } else { - arr[i] = (arr[i] & 0x0F) | (value << 4); - } -} - -pub fn flatten_anvil_sections( - level: &HashMap, - section_shift: Option, - context: &mut ChunkConversionContext, -) -> ([u8; CHUNK_BLOCKS], [u8; CHUNK_NIBBLES], [u8; CHUNK_NIBBLES], [u8; CHUNK_NIBBLES]) { - let mut blocks = [0u8; CHUNK_BLOCKS]; - let mut data = [0u8; CHUNK_NIBBLES]; - let mut sky_light = [0xFFu8; CHUNK_NIBBLES]; - let mut block_light = [0u8; CHUNK_NIBBLES]; - let sections = NbtHelper::get_list(level, "Sections") - .or_else(|| NbtHelper::get_list(level, "sections")); - - let sections = match sections { - Some(s) => s, - None => return (blocks, data, sky_light, block_light), - }; - - struct DecodedSectionData { - section_y: i32, - sblocks: [u8; 4096], - sdata: [u8; 2048], - sky: Option<[u8; 2048]>, - block: Option<[u8; 2048]>, - non_air: i32, - } - - let mut decoded = Vec::new(); - for section_tag in §ions { - let section = match section_tag.as_compound() { - Some(s) => s, - None => continue, - }; - - let section_y = NbtHelper::get_byte(section, "Y") - .or_else(|| NbtHelper::get_int(section, "y").map(|v| v as i8)) - .unwrap_or(0) as i32; - - let (sblocks, sdata) = match try_decode_section_blocks(section, context) { - Some(r) => r, - None => continue, - }; - - let s_sky = NbtHelper::get_byte_array(section, "SkyLight") - .or_else(|| NbtHelper::get_byte_array(section, "sky_light")) - .map(|v| { - let mut arr = [0u8; 2048]; - let len = v.len().min(2048); - arr[..len].copy_from_slice(&v[..len]); - arr - }); - - let s_block = NbtHelper::get_byte_array(section, "BlockLight") - .or_else(|| NbtHelper::get_byte_array(section, "block_light")) - .map(|v| { - let mut arr = [0u8; 2048]; - let len = v.len().min(2048); - arr[..len].copy_from_slice(&v[..len]); - arr - }); - - let non_air = sblocks.iter().filter(|&&b| b != 0).count() as i32; - decoded.push(DecodedSectionData { - section_y, - sblocks, - sdata, - sky: s_sky, - block: s_block, - non_air, - }); - } - - if decoded.is_empty() { - return (blocks, data, sky_light, block_light); - } - - let mut effective_shift = section_shift.unwrap_or(0); - let uses_negative_y = decoded.iter().any(|s| s.section_y < 0); - if effective_shift == 0 && section_shift.is_none() { - let anchor = decoded.iter() - .max_by(|a, b| a.non_air.cmp(&b.non_air) - .then_with(|| (a.section_y - 4).abs().cmp(&(b.section_y - 4).abs()))) - .map(|s| s.section_y) - .unwrap_or(4); - effective_shift = if uses_negative_y { anchor + 4 } else { anchor - 4 }; - } - - for section in &decoded { - let remapped_y = section.section_y - effective_shift; - if remapped_y < 0 || remapped_y > 15 { - continue; - } - let base_y = remapped_y as usize * 16; - for i in 0..4096 { - let x = i & 0x0F; - let z = (i >> 4) & 0x0F; - let y = (i >> 8) & 0x0F; - let global_y = base_y + y; - let flat_index = ((x * 16) + z) * 256 + global_y; - blocks[flat_index] = section.sblocks[i]; - set_nibble(&mut data, flat_index, get_nibble(§ion.sdata, i)); - if let Some(ref sk) = section.sky { - set_nibble(&mut sky_light, flat_index, get_nibble(sk, i)); - } - if let Some(ref bl) = section.block { - set_nibble(&mut block_light, flat_index, get_nibble(bl, i)); - } - } - } - - (blocks, data, sky_light, block_light) -} - -fn try_decode_section_blocks(section: &HashMap, context: &mut ChunkConversionContext) -> Option<([u8; 4096], [u8; 2048])> { - let old_blocks = NbtHelper::get_byte_array(section, "Blocks"); - if let Some(ref old_b) = old_blocks { - if old_b.len() >= 4096 { - let mut blocks = [0u8; 4096]; - blocks.copy_from_slice(&old_b[..4096]); - let data_arr = NbtHelper::get_byte_array_or_default(section, "Data", 2048); - let mut data = [0u8; 2048]; - let len = data_arr.len().min(2048); - data[..len].copy_from_slice(&data_arr[..len]); - return Some((blocks, data)); - } - } - - try_decode_palette_section(section, context) -} - -fn try_decode_palette_section(section: &HashMap, context: &mut ChunkConversionContext) -> Option<([u8; 4096], [u8; 2048])> { - let block_states_container = NbtHelper::get_compound(section, "block_states"); - let palette = NbtHelper::get_list(section, "Palette") - .or_else(|| block_states_container.as_ref().and_then(|c| NbtHelper::get_list(c, "palette")))?; - - if palette.is_empty() { - return None; - } - - let mut blocks = [0u8; 4096]; - let mut data = [0u8; 2048]; - if palette.len() == 1 { - let entry = match &palette[0] { - NbtTag::Compound(m) => m, - _ => return None, - }; - let legacy = map_palette_entry(entry, context); - for b in blocks.iter_mut() { - *b = legacy.id; - } - if legacy.data != 0 { - for i in 0..4096 { - set_nibble(&mut data, i, legacy.data); - } - } - return Some((blocks, data)); - } - - let block_states = section.get("BlockStates") - .and_then(|t| t.get_long_array()) - .or_else(|| block_states_container.as_ref().and_then(|c| c.get("data").and_then(|t| t.get_long_array())))?; - - if block_states.is_empty() { - return None; - } - - let bits_per_block = std::cmp::max(4, bits_required(palette.len() - 1)); - let values_per_long = std::cmp::max(1, 64 / bits_per_block); - let expected_long_count = (4096 + values_per_long - 1) / values_per_long; - let use_padded = block_states.len() == expected_long_count as usize; - for i in 0..4096 { - let palette_index = if use_padded { - read_padded_block_state(block_states, bits_per_block, i) - } else { - read_compact_block_state(block_states, bits_per_block, i) - }; - - if palette_index >= palette.len() { - continue; - } - - let entry = match &palette[palette_index] { - NbtTag::Compound(m) => m, - _ => continue, - }; - - let legacy = map_palette_entry(entry, context); - blocks[i] = legacy.id; - if legacy.data != 0 { - set_nibble(&mut data, i, legacy.data); - } - } - - Some((blocks, data)) -} - -fn map_palette_entry(entry: &HashMap, context: &mut ChunkConversionContext) -> LegacyBlockState { - let name = NbtHelper::get_string(entry, "Name").unwrap_or_default(); - let properties = NbtHelper::get_compound(entry, "Properties").unwrap_or_default(); - let mut prop_map = HashMap::new(); - for (k, v) in &properties { - if let Some(s) = v.get_string() { - prop_map.insert(k.clone(), s.to_string()); - } - } - map_modern_block_state(&name, &prop_map, context) -} - -fn bits_required(value: usize) -> i32 { - if value == 0 { return 1; } - let mut bits = 0; - let mut v = value; - while v > 0 { - bits += 1; - v >>= 1; - } - bits -} - -fn read_padded_block_state(states: &[i64], bits_per_block: i32, index: usize) -> usize { - let values_per_long = std::cmp::max(1, 64 / bits_per_block); - let long_index = index / values_per_long as usize; - let bit_offset = (index % values_per_long as usize) * bits_per_block as usize; - let mask = (1u64 << bits_per_block) - 1; - (states[long_index] as u64 >> bit_offset & mask) as usize -} - -fn read_compact_block_state(states: &[i64], bits_per_block: i32, index: usize) -> usize { - let start_bit = index * bits_per_block as usize; - let long_index = start_bit >> 6; - let bit_offset = start_bit & 63; - let mask = (1u64 << bits_per_block) - 1; - let mut value = states[long_index] as u64 >> bit_offset; - let bits_read = 64 - bit_offset; - if (bits_read as usize) < bits_per_block as usize && long_index + 1 < states.len() { - value |= (states[long_index + 1] as u64) << bits_read; - } - (value & mask) as usize -} diff --git a/src-tauri/src/console2lce/nbt_util.rs b/src-tauri/src/console2lce/nbt_util.rs deleted file mode 100644 index bdf7c4a..0000000 --- a/src-tauri/src/console2lce/nbt_util.rs +++ /dev/null @@ -1,453 +0,0 @@ -use std::collections::HashMap; -use std::io::{Read, Write, Cursor}; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; -use flate2::read::GzDecoder; -use flate2::read::ZlibDecoder; -use flate2::write::ZlibEncoder; -use flate2::Compression as FlateCompression; -use crate::console2lce::error::ConversionError; -#[derive(Debug, Clone)] -pub enum NbtTag { - Byte(i8), - Short(i16), - Int(i32), - Long(i64), - Float(f32), - Double(f64), - String(String), - ByteArray(Vec), - IntArray(Vec), - LongArray(Vec), - List(Vec, u8), - Compound(HashMap), - End, -} - -impl NbtTag { - pub fn get_byte(&self) -> Option { - if let NbtTag::Byte(v) = self { Some(*v) } else { None } - } - - pub fn get_short(&self) -> Option { - if let NbtTag::Short(v) = self { Some(*v) } else { None } - } - - pub fn get_int(&self) -> Option { - if let NbtTag::Int(v) = self { Some(*v) } else { None } - } - - pub fn get_long(&self) -> Option { - if let NbtTag::Long(v) = self { Some(*v) } else { None } - } - - pub fn get_float(&self) -> Option { - if let NbtTag::Float(v) = self { Some(*v) } else { None } - } - - pub fn get_double(&self) -> Option { - if let NbtTag::Double(v) = self { Some(*v) } else { None } - } - - pub fn get_string(&self) -> Option<&str> { - if let NbtTag::String(v) = self { Some(v.as_str()) } else { None } - } - - pub fn get_byte_array(&self) -> Option<&[u8]> { - if let NbtTag::ByteArray(v) = self { Some(v.as_slice()) } else { None } - } - - pub fn get_int_array(&self) -> Option<&[i32]> { - if let NbtTag::IntArray(v) = self { Some(v.as_slice()) } else { None } - } - - pub fn get_long_array(&self) -> Option<&[i64]> { - if let NbtTag::LongArray(v) = self { Some(v.as_slice()) } else { None } - } - - pub fn get_list(&self) -> Option<(&[NbtTag], u8)> { - if let NbtTag::List(tags, type_id) = self { - Some((tags.as_slice(), *type_id)) - } else { - None - } - } - - pub fn get_compound(&self) -> Option<&HashMap> { - if let NbtTag::Compound(map) = self { Some(map) } else { None } - } - - pub fn get_compound_mut(&mut self) -> Option<&mut HashMap> { - if let NbtTag::Compound(map) = self { Some(map) } else { None } - } - - pub fn as_compound(&self) -> Option<&HashMap> { - self.get_compound() - } - - pub fn lookup(&self, path: &[&str]) -> Option<&NbtTag> { - let mut current = self; - for key in path { - match current { - NbtTag::Compound(map) => { - current = map.get(*key)?; - } - _ => return None, - } - } - Some(current) - } - - pub fn lookup_mut(&mut self, path: &[&str]) -> Option<&mut NbtTag> { - let mut current = self; - for key in path { - match current { - NbtTag::Compound(map) => { - current = map.get_mut(*key)?; - } - _ => return None, - } - } - Some(current) - } -} - -fn read_string(reader: &mut R) -> Result { - let len = reader.read_u16::()? as usize; - let mut buf = vec![0u8; len]; - reader.read_exact(&mut buf)?; - Ok(String::from_utf8(buf).map_err(|e| ConversionError::NbtError(format!("Invalid UTF-8: {}", e)))?) -} - -fn write_string(writer: &mut W, s: &str) -> Result<(), ConversionError> { - let bytes = s.as_bytes(); - if bytes.len() > u16::MAX as usize { - return Err(ConversionError::NbtError("String too long".to_string())); - } - writer.write_u16::(bytes.len() as u16)?; - writer.write_all(bytes)?; - Ok(()) -} - -fn read_tag(reader: &mut R, tag_type: u8) -> Result { - match tag_type { - 0 => Ok(NbtTag::End), - 1 => Ok(NbtTag::Byte(reader.read_i8()?)), - 2 => Ok(NbtTag::Short(reader.read_i16::()?)), - 3 => Ok(NbtTag::Int(reader.read_i32::()?)), - 4 => Ok(NbtTag::Long(reader.read_i64::()?)), - 5 => Ok(NbtTag::Float(reader.read_f32::()?)), - 6 => Ok(NbtTag::Double(reader.read_f64::()?)), - 7 => { - let len = reader.read_i32::()?; - if len < 0 { - return Err(ConversionError::NbtError("Negative byte array length".to_string())); - } - let mut buf = vec![0u8; len as usize]; - reader.read_exact(&mut buf)?; - Ok(NbtTag::ByteArray(buf)) - } - 8 => Ok(NbtTag::String(read_string(reader)?)), - 9 => { - let element_type = reader.read_u8()?; - let len = reader.read_i32::()?; - if len < 0 { - return Err(ConversionError::NbtError("Negative list length".to_string())); - } - let mut tags = Vec::with_capacity(len as usize); - for _ in 0..len { - tags.push(read_tag(reader, element_type)?); - } - Ok(NbtTag::List(tags, element_type)) - } - 10 => { - let mut map = HashMap::new(); - loop { - let item_type = reader.read_u8()?; - if item_type == 0 { break; } - let name = read_string(reader)?; - let tag = read_tag(reader, item_type)?; - map.insert(name, tag); - } - Ok(NbtTag::Compound(map)) - } - 11 => { - let len = reader.read_i32::()?; - if len < 0 { - return Err(ConversionError::NbtError("Negative int array length".to_string())); - } - let mut buf = Vec::with_capacity(len as usize); - for _ in 0..len { - buf.push(reader.read_i32::()?); - } - Ok(NbtTag::IntArray(buf)) - } - 12 => { - let len = reader.read_i32::()?; - if len < 0 { - return Err(ConversionError::NbtError("Negative long array length".to_string())); - } - let mut buf = Vec::with_capacity(len as usize); - for _ in 0..len { - buf.push(reader.read_i64::()?); - } - Ok(NbtTag::LongArray(buf)) - } - _ => Err(ConversionError::NbtError(format!("Unknown tag type: {}", tag_type))), - } -} - -fn write_tag(writer: &mut W, tag: &NbtTag) -> Result<(), ConversionError> { - match tag { - NbtTag::End => writer.write_u8(0)?, - NbtTag::Byte(v) => writer.write_i8(*v)?, - NbtTag::Short(v) => writer.write_i16::(*v)?, - NbtTag::Int(v) => writer.write_i32::(*v)?, - NbtTag::Long(v) => writer.write_i64::(*v)?, - NbtTag::Float(v) => writer.write_f32::(*v)?, - NbtTag::Double(v) => writer.write_f64::(*v)?, - NbtTag::ByteArray(v) => { - writer.write_i32::(v.len() as i32)?; - writer.write_all(v)?; - } - NbtTag::String(s) => write_string(writer, s)?, - NbtTag::List(tags, _) => { - let element_type = if tags.is_empty() { 0 } else { get_tag_type(&tags[0]) }; - writer.write_u8(element_type)?; - writer.write_i32::(tags.len() as i32)?; - for tag in tags { - write_tag(writer, tag)?; - } - } - NbtTag::Compound(map) => { - for (name, tag) in map { - let tag_type = get_tag_type(tag); - writer.write_u8(tag_type)?; - write_string(writer, name)?; - write_tag(writer, tag)?; - } - writer.write_u8(0)?; - } - NbtTag::IntArray(v) => { - writer.write_i32::(v.len() as i32)?; - for val in v { - writer.write_i32::(*val)?; - } - } - NbtTag::LongArray(v) => { - writer.write_i32::(v.len() as i32)?; - for val in v { - writer.write_i64::(*val)?; - } - } - } - Ok(()) -} - -fn get_tag_type(tag: &NbtTag) -> u8 { - match tag { - NbtTag::End => 0, - NbtTag::Byte(_) => 1, - NbtTag::Short(_) => 2, - NbtTag::Int(_) => 3, - NbtTag::Long(_) => 4, - NbtTag::Float(_) => 5, - NbtTag::Double(_) => 6, - NbtTag::ByteArray(_) => 7, - NbtTag::String(_) => 8, - NbtTag::List(_, _) => 9, - NbtTag::Compound(_) => 10, - NbtTag::IntArray(_) => 11, - NbtTag::LongArray(_) => 12, - } -} - -pub fn read_nbt(data: &[u8]) -> Result<(String, NbtTag), ConversionError> { - let mut reader = Cursor::new(data); - let root_type = reader.read_u8()?; - if root_type == 0 { - return Ok((String::new(), NbtTag::End)); - } - let name = read_string(&mut reader)?; - let tag = read_tag(&mut reader, root_type)?; - Ok((name, tag)) -} - -pub fn write_nbt(name: &str, tag: &NbtTag) -> Result, ConversionError> { - let mut buf = Vec::new(); - let tag_type = get_tag_type(tag); - buf.write_u8(tag_type)?; - write_string(&mut buf, name)?; - write_tag(&mut buf, tag)?; - Ok(buf) -} - -pub fn read_gzipped_nbt(data: &[u8]) -> Result<(String, NbtTag), ConversionError> { - let mut decoder = GzDecoder::new(data); - let mut decompressed = Vec::new(); - decoder.read_to_end(&mut decompressed)?; - read_nbt(&decompressed) -} - -pub fn read_zlibbed_nbt(data: &[u8]) -> Result<(String, NbtTag), ConversionError> { - let mut decoder = ZlibDecoder::new(data); - let mut decompressed = Vec::new(); - decoder.read_to_end(&mut decompressed)?; - read_nbt(&decompressed) -} - -pub fn write_zlibbed_nbt(name: &str, tag: &NbtTag) -> Result, ConversionError> { - let nbt_data = write_nbt(name, tag)?; - let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default()); - encoder.write_all(&nbt_data)?; - let compressed = encoder.finish()?; - Ok(compressed) -} - -pub fn write_nbt_le(tag: &NbtTag) -> Result, ConversionError> { - let mut buf = Vec::new(); - let tag_type = get_tag_type(tag); - buf.write_u8(tag_type)?; - write_tag_le(&mut buf, tag)?; - Ok(buf) -} - -fn write_tag_le(writer: &mut W, tag: &NbtTag) -> Result<(), ConversionError> { - match tag { - NbtTag::End => writer.write_u8(0)?, - NbtTag::Byte(v) => writer.write_i8(*v)?, - NbtTag::Short(v) => writer.write_i16::(*v)?, - NbtTag::Int(v) => writer.write_i32::(*v)?, - NbtTag::Long(v) => writer.write_i64::(*v)?, - NbtTag::Float(v) => writer.write_f32::(*v)?, - NbtTag::Double(v) => writer.write_f64::(*v)?, - NbtTag::ByteArray(v) => { - writer.write_i32::(v.len() as i32)?; - writer.write_all(v)?; - } - NbtTag::String(s) => { - let bytes = s.as_bytes(); - writer.write_u16::(bytes.len() as u16)?; - writer.write_all(bytes)?; - } - NbtTag::List(tags, _) => { - let element_type = if tags.is_empty() { 0 } else { get_tag_type(&tags[0]) }; - writer.write_u8(element_type)?; - writer.write_i32::(tags.len() as i32)?; - for t in tags { - write_tag_le(writer, t)?; - } - } - NbtTag::Compound(map) => { - for (name, t) in map { - let tt = get_tag_type(t); - writer.write_u8(tt)?; - let bytes = name.as_bytes(); - writer.write_u16::(bytes.len() as u16)?; - writer.write_all(bytes)?; - write_tag_le(writer, t)?; - } - writer.write_u8(0)?; - } - NbtTag::IntArray(v) => { - writer.write_i32::(v.len() as i32)?; - for val in v { - writer.write_i32::(*val)?; - } - } - NbtTag::LongArray(v) => { - writer.write_i32::(v.len() as i32)?; - for val in v { - writer.write_i64::(*val)?; - } - } - } - Ok(()) -} - -pub fn read_mcr_chunk(data: &[u8], _compressed_size: u32) -> Result, ConversionError> { - if data.len() < 5 { - return Err(ConversionError::InvalidFormat("Chunk data too short".to_string())); - } - let compression_scheme = data[4]; - let payload = &data[5..]; - match compression_scheme { - 1 => { - let mut decoder = GzDecoder::new(payload); - let mut decompressed = Vec::new(); - decoder.read_to_end(&mut decompressed)?; - Ok(decompressed) - } - 2 => { - let mut decoder = ZlibDecoder::new(payload); - let mut decompressed = Vec::new(); - decoder.read_to_end(&mut decompressed)?; - Ok(decompressed) - } - 3 => { - Ok(payload.to_vec()) - } - _ => Err(ConversionError::DecompressionFailed( - format!("Unknown compression scheme: {}", compression_scheme) - )), - } -} - -#[derive(Debug, Clone)] -pub struct NbtHelper; -impl NbtHelper { - pub fn get_byte_array(tag: &HashMap, name: &str) -> Option> { - tag.get(name).and_then(|t| t.get_byte_array().map(|v| v.to_vec())) - } - - pub fn get_byte_array_or_default(tag: &HashMap, name: &str, default_size: usize) -> Vec { - Self::get_byte_array(tag, name).map(|v| { - if v.len() >= default_size { - v[..default_size].to_vec() - } else { - let mut padded = vec![0u8; default_size]; - padded[..v.len()].copy_from_slice(&v); - padded - } - }).unwrap_or_else(|| vec![0u8; default_size]) - } - - pub fn get_int(tag: &HashMap, name: &str) -> Option { - tag.get(name).and_then(|t| t.get_int()) - } - - pub fn get_long(tag: &HashMap, name: &str) -> Option { - tag.get(name).and_then(|t| t.get_long()) - } - - pub fn get_short(tag: &HashMap, name: &str) -> Option { - tag.get(name).and_then(|t| t.get_short()) - } - - pub fn get_byte(tag: &HashMap, name: &str) -> Option { - tag.get(name).and_then(|t| t.get_byte()) - } - - pub fn get_string(tag: &HashMap, name: &str) -> Option { - tag.get(name).and_then(|t| t.get_string().map(|s| s.to_string())) - } - - pub fn get_list(tag: &HashMap, name: &str) -> Option> { - tag.get(name).and_then(|t| { - if let NbtTag::List(tags, _) = t { - Some(tags.clone()) - } else { - None - } - }) - } - - pub fn get_compound(tag: &HashMap, name: &str) -> Option> { - tag.get(name).and_then(|t| { - if let NbtTag::Compound(map) = t { - Some(map.clone()) - } else { - None - } - }) - } -} diff --git a/src-tauri/src/console2lce/region.rs b/src-tauri/src/console2lce/region.rs deleted file mode 100644 index 414640f..0000000 --- a/src-tauri/src/console2lce/region.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::console2lce::error::ConversionError; -const SECTOR_SIZE: u32 = 4096; -const REGION_SIZE_X: usize = 32; -const REGION_SIZE_Z: usize = 32; -#[derive(Debug, Clone, Default)] -pub struct RegionEntry { - pub offset: u32, - pub sector_count: u32, - pub timestamp: u32, - pub exists: bool, -} - -#[derive(Debug, Clone)] -pub struct Xbox360Region { - pub entries: [[RegionEntry; REGION_SIZE_Z]; REGION_SIZE_X], - pub raw_data: Vec, -} - -pub fn parse_xbox360_region(data: &[u8]) -> Result { - if data.len() < 8192 { - return Err(ConversionError::InvalidFormat("Region data too short, need at least 8192 bytes for header".to_string())); - } - - let mut region = Xbox360Region { - entries: Default::default(), - raw_data: data.to_vec(), - }; - - for z in 0..REGION_SIZE_Z { - for x in 0..REGION_SIZE_X { - let index = z * REGION_SIZE_X + x; - let offset_raw = read_be_u32(&data[index * 4..index * 4 + 4]); - let timestamp_raw = read_be_u32(&data[4096 + index * 4..4096 + index * 4 + 4]); - let offset = offset_raw >> 8; - let sector_count = offset_raw & 0xFF; - region.entries[x][z] = RegionEntry { - offset, - sector_count, - timestamp: timestamp_raw, - exists: offset_raw != 0 && sector_count != 0, - }; - } - } - - Ok(region) -} - -pub fn read_region_chunk(region: &Xbox360Region, x: usize, z: usize) -> Result, ConversionError> { - if x >= REGION_SIZE_X || z >= REGION_SIZE_Z { - return Err(ConversionError::InvalidFormat(format!( - "Chunk coordinates out of range: ({}, {}), max ({}, {})", - x, z, REGION_SIZE_X, REGION_SIZE_Z - ))); - } - - let entry = ®ion.entries[x][z]; - if !entry.exists { - return Err(ConversionError::MissingData(format!( - "Chunk ({}, {}) does not exist in region", x, z - ))); - } - - let sector_offset = (entry.offset as usize) * SECTOR_SIZE as usize; - let total_size = (entry.sector_count as usize) * SECTOR_SIZE as usize; - if sector_offset + total_size > region.raw_data.len() { - return Err(ConversionError::InvalidFormat(format!( - "Chunk data at sector offset {} with size {} exceeds region data length {}", - sector_offset, total_size, region.raw_data.len() - ))); - } - - let sector_data = ®ion.raw_data[sector_offset..sector_offset + 5]; - let _compressed_size = read_be_u32(§or_data[0..4]); - let _compression_scheme = sector_data[4]; - let chunk_data = region.raw_data[sector_offset..sector_offset + total_size].to_vec(); - Ok(chunk_data) -} - -pub fn read_java_mcr_region(data: &[u8]) -> Result)>, ConversionError> { - if data.len() < 8192 { - return Err(ConversionError::InvalidFormat("MCR data too short".to_string())); - } - - let mut chunks = Vec::new(); - for z in 0..REGION_SIZE_Z { - for x in 0..REGION_SIZE_X { - let index = z * REGION_SIZE_X + x; - let offset_raw = read_be_u32(&data[index * 4..index * 4 + 4]); - if offset_raw == 0 { - continue; - } - - let sector_offset = (offset_raw >> 8) as usize; - let _sector_count = (offset_raw & 0xFF) as usize; - let file_offset = sector_offset * SECTOR_SIZE as usize; - if file_offset + 5 > data.len() { - continue; - } - - let chunk_len = read_be_u32(&data[file_offset..file_offset + 4]) as usize; - let compression = data[file_offset + 4]; - if chunk_len == 0 || file_offset + 5 + chunk_len > data.len() { - continue; - } - - let mut chunk_data = vec![0u8; 5 + chunk_len]; - chunk_data[0..4].copy_from_slice(&data[file_offset..file_offset + 4]); - chunk_data[4] = compression; - chunk_data[5..].copy_from_slice(&data[file_offset + 5..file_offset + 5 + chunk_len]); - chunks.push((x, z, chunk_data)); - } - } - - Ok(chunks) -} - -pub fn read_java_mca_region(data: &[u8]) -> Result)>, ConversionError> { - read_java_mcr_region(data) -} - -fn read_be_u32(bytes: &[u8]) -> u32 { - if bytes.len() < 4 { return 0; } - ((bytes[0] as u32) << 24) | ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32) -} diff --git a/src-tauri/src/console2lce/save_data_container.rs b/src-tauri/src/console2lce/save_data_container.rs deleted file mode 100644 index e338009..0000000 --- a/src-tauri/src/console2lce/save_data_container.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::collections::HashMap; -use byteorder::{LittleEndian, WriteBytesExt}; -use crate::console2lce::error::ConversionError; -const SAVE_MAGIC: [u8; 6] = [0x01, 0x00, 0x00, 0x00, 0x01, 0x00]; -const CONTAINER_VERSION: u32 = 2; -pub fn write_save_data_container( - files: &[(String, Vec)], -) -> Result, ConversionError> { - let mut data = Vec::new(); - data.extend_from_slice(&SAVE_MAGIC); - data.write_u32::(CONTAINER_VERSION)?; - data.write_u32::(files.len() as u32)?; - let mut file_offsets = Vec::new(); - for (name, content) in files { - file_offsets.push((name.clone(), data.len() as u64, content.len() as u64)); - data.extend_from_slice(content); - while data.len() % 4 != 0 { - data.push(0); - } - } - - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - - for (name, offset, length) in &file_offsets { - data.write_u64::(*offset)?; - data.write_u64::(*length)?; - data.write_u64::(timestamp)?; - let name_bytes = name.as_bytes(); - data.write_u16::(name_bytes.len() as u16)?; - data.extend_from_slice(name_bytes); - data.push(0); - while data.len() % 4 != 0 { - data.push(0); - } - } - - Ok(data) -} - -pub fn write_save_data_ms( - level_dat: &[u8], - regions: &HashMap>, -) -> Result, ConversionError> { - let mut files = Vec::new(); - files.push(("level.dat".to_string(), level_dat.to_vec())); - for (path, region_data) in regions { - files.push((path.clone(), region_data.clone())); - } - - write_save_data_container(&files) -} diff --git a/src-tauri/src/console2lce/stfs.rs b/src-tauri/src/console2lce/stfs.rs deleted file mode 100644 index 6ef274d..0000000 --- a/src-tauri/src/console2lce/stfs.rs +++ /dev/null @@ -1,635 +0,0 @@ -use crate::console2lce::error::ConversionError; -use lzxd::{Lzxd, WindowSize}; - -const CON_MAGIC: u32 = 0x434F4E20; - -fn read_be_u32(bytes: &[u8]) -> u32 { - ((bytes[0] as u32) << 24) | ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32) -} - -fn read_be_u16(bytes: &[u8]) -> u16 { - ((bytes[0] as u16) << 8) | (bytes[1] as u16) -} - -fn read_le_u16(bytes: &[u8]) -> u16 { - (bytes[0] as u16) | ((bytes[1] as u16) << 8) -} - -fn read_le_u24(bytes: &[u8]) -> i32 { - (bytes[0] as i32) | ((bytes[1] as i32) << 8) | ((bytes[2] as i32) << 16) -} - -pub fn is_stfs_package(data: &[u8]) -> bool { - if data.len() < 4 { return false; } - read_be_u32(&data[0..4]) == CON_MAGIC -} - -struct StfsVD { - _size: u8, - block_separation: u8, - file_table_block_count: u16, - file_table_block_num: i32, - alloc_block_count: u32, - _unallocated_block_count: u32, -} - -fn read_stfs_vd(data: &[u8]) -> Result { - if data.len() < 0x24 { - return Err(ConversionError::InvalidFormat("STFS VD too short".to_string())); - } - let size = data[0]; - let block_separation = data[2]; - let file_table_block_count = read_le_u16(&data[3..5]); - let file_table_block_num = read_le_u24(&data[5..8]); - let alloc_block_count = read_be_u32(&data[0x1C..0x20]); - let unallocated_block_count = read_be_u32(&data[0x20..0x24]); - Ok(StfsVD { - _size: size, - block_separation, - file_table_block_count, - file_table_block_num, - alloc_block_count, - _unallocated_block_count: unallocated_block_count, - }) -} - -struct StfsPackage<'a> { - data: &'a [u8], - header_size: u32, - vd: StfsVD, - package_sex: u8, //neo: 0=female, 1=male - block_step: [u32; 2], - first_hash_addr: u32, - top_level: u8, - top_table: Vec, - alloc_block_count: u32, -} - -#[derive(Clone, Copy)] -struct HashEntry { - _hash: [u8; 20], - status: u8, - next_block: i32, -} - -impl<'a> StfsPackage<'a> { - fn parse(data: &'a [u8]) -> Result { - if data.len() < 0x400 { - return Err(ConversionError::InvalidFormat("STFS data too short for header".to_string())); - } - - if !is_stfs_package(data) { - return Err(ConversionError::InvalidFormat("Not a valid STFS package".to_string())); - } - - let header_size = read_be_u32(&data[0x340..0x344]); - let content_type = read_be_u32(&data[0x344..0x348]); - if content_type != 1 { - return Err(ConversionError::InvalidFormat(format!( - "Not a savegame (content_type={})", content_type - ))); - } - - let file_system = read_be_u32(&data[0x3A9..0x3AD]); - if file_system != 0 { - return Err(ConversionError::InvalidFormat("Not STFS format".to_string())); - } - - let vd = read_stfs_vd(&data[0x379..])?; - let alloc_block_count = vd.alloc_block_count; - if alloc_block_count == 0 { - return Err(ConversionError::InvalidFormat("No allocated blocks".to_string())); - } - - let package_sex = (!vd.block_separation) & 1; - let block_step: [u32; 2] = if package_sex == 0 { - [0xAB, 0x718F] - } else { - [0xAC, 0x723A] - }; - - let first_hash_addr = (header_size + 0x0FFF) & !0x0FFF; - let top_level = if alloc_block_count <= 0xAA { - 0 - } else if alloc_block_count <= 0x70E4 { - 1 - } else if alloc_block_count <= 0x4AF768 { - 2 - } else { - return Err(ConversionError::InvalidFormat("Too many allocated blocks".to_string())); - }; - - let top_true_block = compute_level_n_backing_hash_block_number(0, top_level, &block_step, package_sex); - let base_addr = (top_true_block << 12) + first_hash_addr; - let top_addr = base_addr + (((vd.block_separation & 2) as u32) << 11); - let top_addr = top_addr as usize; - let data_blocks_per_level: [u32; 3] = [1, 0xAA, 0x70E4]; - let divisor = data_blocks_per_level[top_level as usize]; - let mut entry_count = alloc_block_count / divisor; - if alloc_block_count > 0x70E4 && alloc_block_count % 0x70E4 != 0 { - entry_count += 1; - } else if alloc_block_count > 0xAA && alloc_block_count % 0xAA != 0 { - entry_count += 1; - } - - let mut top_table = Vec::with_capacity(entry_count as usize); - for i in 0..entry_count { - let off = top_addr + (i as usize) * 24; - if off + 24 > data.len() { - break; - } - let mut hash = [0u8; 20]; - hash.copy_from_slice(&data[off..off + 20]); - let status = data[off + 20]; - let next_block = read_le_u24(&data[off + 21..off + 24]); - top_table.push(HashEntry { - _hash: hash, - status, - next_block, - }); - } - - Ok(StfsPackage { - data, - header_size, - vd, - package_sex, - block_step, - first_hash_addr, - top_level, - top_table, - alloc_block_count, - }) - } - - fn block_to_address(&self, block_num: u32) -> u32 { - (self.compute_backing_data_block_number(block_num) << 12) + self.first_hash_addr - } - - fn compute_backing_data_block_number(&self, block_num: u32) -> u32 { - let psex = self.package_sex as u32; - let to_return = (((block_num + 0xAA) / 0xAA) << psex) + block_num; - if block_num < 0xAA { - return to_return; - } else if block_num < 0x70E4 { - return to_return + (((block_num + 0x70E4) / 0x70E4) << psex); - } else { - return (1 << psex) + (to_return + (((block_num + 0x70E4) / 0x70E4) << psex)); - } - } - - fn get_hash_entry(&self, block_num: u32) -> Result { - if block_num >= self.alloc_block_count { - return Err(ConversionError::InvalidFormat("Block number out of range".to_string())); - } - let hash_addr = self.get_hash_address_of_block(block_num); - let off = hash_addr as usize; - if off + 24 > self.data.len() { - return Err(ConversionError::InvalidFormat("Hash address out of file bounds".to_string())); - } - let mut hash = [0u8; 20]; - hash.copy_from_slice(&self.data[off..off + 20]); - let status = self.data[off + 20]; - let next_block = read_le_u24(&self.data[off + 21..off + 24]); - Ok(HashEntry { _hash: hash, status, next_block }) - } - - fn get_hash_address_of_block(&self, block_num: u32) -> u32 { - let level0 = compute_level_0_backing_hash_block_number(block_num, &self.block_step, self.package_sex); - let mut hash_addr = (level0 << 12) + self.first_hash_addr; - hash_addr += (block_num % 0xAA) * 24; - match self.top_level { - 0 => { - hash_addr += (self.vd.block_separation as u32 & 2) << 11; - } - 1 => { - let idx = (block_num / 0xAA) as usize; - if idx < self.top_table.len() { - hash_addr += ((self.top_table[idx].status & 0x40) as u32) << 6; - } - } - 2 => { - let l1_idx = (block_num / 0x70E4) as usize; - if l1_idx < self.top_table.len() { - let l1_off = (self.top_table[l1_idx].status as u32 & 0x40) << 6; - let l1_block = compute_level_1_backing_hash_block_number(block_num, &self.block_step, self.package_sex); - let pos = ((l1_block << 12) + self.first_hash_addr + l1_off) + - ((block_num % 0xAA) * 24); - let pos = pos as usize; - if pos + 21 <= self.data.len() { - hash_addr += (self.data[pos + 20] as u32 & 0x40) << 6; - } - } - } - _ => {} - } - hash_addr - } - - fn read_file_listing(&self) -> Result, ConversionError> { - let mut entries = Vec::new(); - let block_count = self.vd.file_table_block_count as u32; - let mut block = self.vd.file_table_block_num as u32; - for _ in 0..block_count { - let addr = self.block_to_address(block) as usize; - if addr + 0x1000 > self.data.len() { - break; - } - for i in 0..0x40u32 { - let off = addr + (i as usize) * 0x40; - if off + 0x40 > self.data.len() { - break; - } - let name_bytes = &self.data[off..off + 0x28]; - let name_len_byte = self.data[off + 0x28]; - let name_len = name_len_byte & 0x3F; - if name_len == 0 { - continue; - } - let name = String::from_utf8_lossy(&name_bytes[..name_len as usize]).to_string(); - if name.is_empty() { - break; - } - let blocks_for_file = read_le_u24(&self.data[off + 0x29..off + 0x2C]); - let starting_block_num = read_le_u24(&self.data[off + 0x2F..off + 0x32]); - let path_indicator = read_be_u16(&self.data[off + 0x32..off + 0x34]); - let file_size = read_be_u32(&self.data[off + 0x34..off + 0x38]); - let created_ts = read_be_u32(&self.data[off + 0x38..off + 0x3C]); - let accessed_ts = read_be_u32(&self.data[off + 0x3C..off + 0x40]); - let flags = name_len_byte >> 6; - entries.push(StfsFileEntry { - name, - flags, - blocks_for_file, - starting_block_num, - path_indicator, - file_size, - created_ts, - accessed_ts, - }); - } - - let he = self.get_hash_entry(block)?; - block = he.next_block as u32; - } - - Ok(entries) - } - - fn extract_file(&self, entry: &StfsFileEntry) -> Result, ConversionError> { - let file_size = entry.file_size; - if file_size == 0 { - return Ok(Vec::new()); - } - - if entry.flags & 1 != 0 { - let mut out = Vec::with_capacity(file_size as usize); - let start_addr = self.block_to_address(entry.starting_block_num as u32); - let pos = start_addr as usize; - let block_count = (compute_level_0_backing_hash_block_number( - entry.starting_block_num as u32, &self.block_step, self.package_sex - ) + self.block_step[0]) - ((start_addr - self.first_hash_addr) >> 12); - let initial = if (entry.blocks_for_file as u32) <= block_count { - let sz = entry.file_size as usize; - if pos + sz <= self.data.len() { - out.extend_from_slice(&self.data[pos..pos + sz]); - } - return Ok(out); - } else { - let amount = (block_count << 12) as usize; - if pos + amount <= self.data.len() { - out.extend_from_slice(&self.data[pos..pos + amount]); - } - amount - }; - - let mut remaining = (file_size as usize).wrapping_sub(initial); - let mut cur_pos = pos + initial; - while remaining >= 0xAA000 { - cur_pos += self.get_hash_table_skip_size(cur_pos as u32) as usize; - let end = (cur_pos + 0xAA000).min(self.data.len()); - out.extend_from_slice(&self.data[cur_pos..end]); - remaining = remaining.wrapping_sub(0xAA000); - cur_pos = end; - } - if remaining > 0 { - cur_pos += self.get_hash_table_skip_size(cur_pos as u32) as usize; - let end = (cur_pos + remaining).min(self.data.len()); - out.extend_from_slice(&self.data[cur_pos..end]); - } - Ok(out) - } else { - let full_reads = file_size / 0x1000; - let remainder = file_size % 0x1000; - let mut out = Vec::with_capacity(file_size as usize); - let mut block = entry.starting_block_num as u32; - for _ in 0..full_reads { - let addr = self.block_to_address(block) as usize; - if addr + 0x1000 <= self.data.len() { - out.extend_from_slice(&self.data[addr..addr + 0x1000]); - } - block = self.get_hash_entry(block)?.next_block as u32; - } - if remainder > 0 { - let addr = self.block_to_address(block) as usize; - if addr + remainder as usize <= self.data.len() { - out.extend_from_slice(&self.data[addr..addr + remainder as usize]); - } - } - Ok(out) - } - } - - fn get_hash_table_skip_size(&self, table_addr: u32) -> u32 { - let psex = self.package_sex as u32; - let true_block = (table_addr - self.first_hash_addr) >> 12; - if true_block == 0 { - return 0x1000 << psex; - } - let mut tb = true_block; - if tb == self.block_step[1] { - return 0x3000 << psex; - } else if tb > self.block_step[1] { - tb -= self.block_step[1] + (1 << psex); - } - if tb == self.block_step[0] || tb % self.block_step[1] == 0 { - return 0x2000 << psex; - } - 0x1000 << psex - } -} - -#[derive(Debug, Clone)] -struct StfsFileEntry { - name: String, - flags: u8, - blocks_for_file: i32, - starting_block_num: i32, - path_indicator: u16, - file_size: u32, - created_ts: u32, - accessed_ts: u32, -} - -fn compute_level_n_backing_hash_block_number( - block_num: u32, level: u8, block_step: &[u32; 2], package_sex: u8, -) -> u32 { - match level { - 0 => compute_level_0_backing_hash_block_number(block_num, block_step, package_sex), - 1 => compute_level_1_backing_hash_block_number(block_num, block_step, package_sex), - 2 => block_step[1], - _ => 0, - } -} - -fn compute_level_0_backing_hash_block_number( - block_num: u32, block_step: &[u32; 2], package_sex: u8, -) -> u32 { - let psex = package_sex as u32; - if block_num < 0xAA { return 0; } - let mut num = (block_num / 0xAA) * block_step[0]; - num += ((block_num / 0x70E4) + 1) << psex; - if block_num / 0x70E4 == 0 { return num; } - num + (1 << psex) -} - -fn compute_level_1_backing_hash_block_number( - block_num: u32, block_step: &[u32; 2], package_sex: u8, -) -> u32 { - let psex = package_sex as u32; - if block_num < 0x70E4 { return block_step[0]; } - (1 << psex) + (block_num / 0x70E4) * block_step[1] -} - -pub fn extract_savegame_from_stfs(data: &[u8]) -> Result, ConversionError> { - let pkg = StfsPackage::parse(data)?; - let listing = pkg.read_file_listing()?; - let entry = listing.iter().find(|e| e.name == "savegame.dat") - .ok_or_else(|| ConversionError::InvalidFormat("savegame.dat not found in STFS listing".to_string()))?; - - eprintln!("[stfs] found savegame.dat: size={} blocks={} start_block={}", - entry.file_size, entry.blocks_for_file, entry.starting_block_num); - - let raw_data = pkg.extract_file(entry)?; - eprintln!("[stfs] extracted {} bytes of savegame.dat", raw_data.len()); - - Ok(raw_data) -} - -pub fn try_decompress_stfs_savegame(data: &[u8]) -> Result, ConversionError> { - if data.len() < 12 { - return Err(ConversionError::InvalidFormat("STFS savegame data too short for header".to_string())); - } - let total_size = read_be_u32(&data[0..4]); - let decompressed_size = u64::from(read_be_u32(&data[4..8])) << 32 | read_be_u32(&data[8..12]) as u64; - let src_size = total_size.wrapping_sub(8) as usize; - let decompressed_size = decompressed_size as usize; - eprintln!("[stfs] savegame header: total_size={} src_size={} decompressed_size={}", - total_size, src_size, decompressed_size); - - if src_size == 0 || src_size > data.len().saturating_sub(12) { - return Err(ConversionError::InvalidFormat(format!( - "Invalid compressed size: {} (data len: {})", src_size, data.len() - ))); - } - if decompressed_size == 0 || decompressed_size > 100_000_000 { - return Err(ConversionError::InvalidFormat(format!( - "Invalid decompressed size: {}", decompressed_size - ))); - } - - let compressed = &data[12..12 + src_size]; - decompress_lzx_chunked(compressed, decompressed_size) -} - -pub fn decompress_lzx_chunked(data: &[u8], decompressed_size: usize) -> Result, ConversionError> { - let mut lzxd = Lzxd::new(WindowSize::KB64); - let mut output = Vec::with_capacity(decompressed_size); - let mut pos = 0; - let mut num_chunks = 0; - let mut comp_count = 0; - let mut uncomp_count = 0; - let mut lzx_fallbacks = 0; - while pos < data.len() && output.len() < decompressed_size { - if pos + 2 > data.len() { - break; - } - let chunk_size = read_be_u16(&data[pos..pos + 2]) as usize; - pos += 2; - if chunk_size == 0 { - break; - } - - let is_compressed = (chunk_size & 0x8000) != 0; - let actual_size = chunk_size & 0x7FFF; - num_chunks += 1; - - if pos + actual_size > data.len() { - eprintln!("[lzx] chunk {} header overrun: need {} have {} at pos {}", - num_chunks, actual_size, data.len() - pos + 2, pos - 2); - break; - } - - let chunk_data = &data[pos..pos + actual_size]; - pos += actual_size; - - if is_compressed { - let out_len = if output.len() + 32768 > decompressed_size { - decompressed_size - output.len() - } else { - 32768 - }; - comp_count += 1; - match lzxd.decompress_next(chunk_data, out_len) { - Ok(decompressed) => { - eprintln!("[lzx] chunk {} C: compressed={} decompressed={} total_so_far={}", - num_chunks, actual_size, decompressed.len(), output.len() + decompressed.len()); - output.extend_from_slice(decompressed); - } - Err(e) => { - return Err(ConversionError::DecompressionFailed( - format!("chunk {} LZXD failed at pos {}: {}", num_chunks, pos - actual_size - 2, e) - )); - } - } - } else { - let mut chunk_lzxd = Lzxd::new(WindowSize::KB32); - match chunk_lzxd.decompress_next(chunk_data, 32768) { - Ok(decompressed) => { - lzx_fallbacks += 1; - eprintln!("[lzx] chunk {} U->C (LZX fallback): {} -> {} total_so_far={}", - num_chunks, actual_size, decompressed.len(), output.len() + decompressed.len()); - output.extend_from_slice(decompressed); - } - Err(_) => { - eprintln!("[lzx] chunk {} U: size={} total_so_far={}", - num_chunks, actual_size, output.len() + actual_size); - output.extend_from_slice(chunk_data); - uncomp_count += 1; - } - } - } - } - eprintln!("[lzx] processed {} chunks ({} marked C + {} LZX-fallback + {} pure U), produced {} of {} expected bytes, consumed {}/{}", - num_chunks, comp_count, lzx_fallbacks, uncomp_count, output.len(), decompressed_size, pos, data.len()); - - output.truncate(decompressed_size); - Ok(output) -} - -pub fn try_decompress_savegame(data: &[u8]) -> Result, ConversionError> { - if data.len() < 8 { - return Err(ConversionError::InvalidFormat("Savegame data too short".to_string())); - } - let magic = &data[0..8]; - let expected_magic = [0x0B, 0xFC, 0x4A, 0x46, 0xAE, 0x7A, 0x43, 0x2B]; - if magic != expected_magic { - return Err(ConversionError::InvalidFormat("Invalid savegame magic".to_string())); - } - - if data.len() < 16 { - return Err(ConversionError::InvalidFormat("Savegame data too short for header".to_string())); - } - - let decompressed_size = read_be_u32(&data[8..12]) as usize; - let compression_type = read_be_u32(&data[12..16]); - let payload = &data[16..]; - match compression_type { - 0 => { - if payload.len() >= decompressed_size { - Ok(payload[..decompressed_size].to_vec()) - } else { - Ok(payload.to_vec()) - } - } - 1 => { - decompress_lzx(payload, decompressed_size) - } - 2 => { - use std::io::Read; - let mut decoder = flate2::read::ZlibDecoder::new(payload); - let mut out = Vec::with_capacity(decompressed_size); - decoder.read_to_end(&mut out)?; - Ok(out) - } - 3 => { - decode_rle(payload, decompressed_size) - } - _ => Err(ConversionError::DecompressionFailed( - format!("Unknown compression type: {}", compression_type) - )), - } -} - -fn decompress_lzx(data: &[u8], decompressed_size: usize) -> Result, ConversionError> { - let mut output = Vec::with_capacity(decompressed_size); - let mut pos = 0; - while pos < data.len() && output.len() < decompressed_size { - if pos + 2 > data.len() { - break; - } - let chunk_size = read_be_u16(&data[pos..pos + 2]) as usize; - pos += 2; - if chunk_size == 0 { - break; - } - - let is_compressed = (chunk_size & 0x8000) != 0; - let actual_size = chunk_size & 0x7FFF; - if is_compressed { - if pos + actual_size > data.len() { - break; - } - let compressed_chunk = &data[pos..pos + actual_size]; - pos += actual_size; - let mut lzxd = Lzxd::new(WindowSize::KB32); - match lzxd.decompress_next(compressed_chunk, 32768) { - Ok(decompressed) => { - output.extend_from_slice(decompressed); - } - Err(e) => { - eprintln!("[lzx] chunk LZXD decompress failed at pos {}: {}; filling with zeros", - pos - actual_size - 2, e); - output.extend_from_slice(&vec![0u8; 32768]); - } - } - } else { - if pos + actual_size > data.len() { - break; - } - output.extend_from_slice(&data[pos..pos + actual_size]); - pos += actual_size; - } - } - - output.truncate(decompressed_size); - Ok(output) -} - -pub fn decode_rle(data: &[u8], decompressed_size: usize) -> Result, ConversionError> { - let mut output = Vec::with_capacity(decompressed_size); - let mut pos = 0; - while pos < data.len() && output.len() < decompressed_size { - let b = data[pos]; - pos += 1; - if b != 0xFF { - if output.len() + 1 > decompressed_size { - break; - } - output.push(b); - } else { - if pos + 1 >= data.len() { - break; - } - let run_len = data[pos] as usize; - let value = data[pos + 1]; - pos += 2; - let actual_len = std::cmp::min(run_len, decompressed_size - output.len()); - for _ in 0..actual_len { - output.push(value); - } - } - } - output.truncate(decompressed_size); - Ok(output) -} diff --git a/src-tauri/src/console2lce/world_profile.rs b/src-tauri/src/console2lce/world_profile.rs deleted file mode 100644 index 0d0d152..0000000 --- a/src-tauri/src/console2lce/world_profile.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[derive(Debug, Clone, Copy)] -pub struct WorldProfile { - pub name: &'static str, - pub xz_size: u32, - pub hell_scale: u32, - pub flat: bool, -} - -pub const PROFILES: &[WorldProfile] = &[ - WorldProfile { name: "classic", xz_size: 54, hell_scale: 54, flat: false }, - WorldProfile { name: "small", xz_size: 64, hell_scale: 64, flat: false }, - WorldProfile { name: "medium", xz_size: 192, hell_scale: 192, flat: false }, - WorldProfile { name: "large", xz_size: 320, hell_scale: 320, flat: false }, - WorldProfile { name: "flat", xz_size: 54, hell_scale: 54, flat: true }, - WorldProfile { name: "flat-small", xz_size: 64, hell_scale: 64, flat: true }, - WorldProfile { name: "flat-medium", xz_size: 192, hell_scale: 192, flat: true }, - WorldProfile { name: "flat-large", xz_size: 320, hell_scale: 320, flat: true }, -]; - -pub fn get_profile(name: &str) -> Option<&'static WorldProfile> { - PROFILES.iter().find(|p| p.name == name) -} - -pub fn world_center_offset(xz_size: u32) -> i32 { - (xz_size as i32) * 8 -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7a34617..8c86541 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,7 +4,6 @@ mod config; mod util; mod platform; mod networking; -pub mod console2lce; mod commands; use std::collections::HashMap; use std::sync::Arc; diff --git a/src/components/modals/ImportWorldModal.tsx b/src/components/modals/ImportWorldModal.tsx index 20f371a..363053e 100644 --- a/src/components/modals/ImportWorldModal.tsx +++ b/src/components/modals/ImportWorldModal.tsx @@ -2,8 +2,6 @@ import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { TauriService } from "../../services/TauriService"; -type WorldType = "java" | "xbox360" | "windows64" | null; - export default function ImportWorldModal({ isOpen, onClose, @@ -19,34 +17,12 @@ export default function ImportWorldModal({ targetInstanceId: string; targetInstanceName: string; }) { - const [worldType, setWorldType] = useState(null); - const [focusIndex, setFocusIndex] = useState(0); const [status, setStatus] = useState(""); const [error, setError] = useState(""); const [isImporting, setIsImporting] = useState(false); - const typeOptions: { id: WorldType; label: string; desc: string }[] = [ - { - id: "java", - label: "Java Edition", - desc: "Import a Java world folder (contains level.dat)", - }, - { - id: "xbox360", - label: "Xbox 360", - desc: "Import an Xbox 360 STFS save (.bin / .dat)", - }, - { - id: "windows64", - label: "Windows64", - desc: "Copy an existing LCE save (.ms file or GameHDD folder)", - }, - ]; - useEffect(() => { if (!isOpen) { - setWorldType(null); - setFocusIndex(0); setStatus(""); setError(""); setIsImporting(false); @@ -54,64 +30,28 @@ export default function ImportWorldModal({ }, [isOpen]); const handleImport = async () => { - if (!worldType || !targetInstanceId) return; + if (!targetInstanceId) return; playPressSound(); setIsImporting(true); setError(""); setStatus("Selecting source..."); try { - let inputPath = ""; - let worldName = ""; - - if (worldType === "java") { - setStatus("Selecting Java world folder..."); - const folder = await TauriService.pickFolder(); - if (!folder) { - setIsImporting(false); - return; - } - inputPath = folder; - worldName = deriveWorldName(folder); - setStatus("Converting Java world to LCE..."); - } else if (worldType === "xbox360") { - setStatus("Selecting Xbox 360 save..."); - const file = await TauriService.pickFile("Select Xbox 360 save", [ - "*.bin", - "*.dat", - "*", - ]); - if (!file) { - setIsImporting(false); - return; - } - inputPath = file; - worldName = deriveWorldName(file); - setStatus("Converting Xbox 360 save to LCE..."); - } else { - setStatus("Selecting LCE save folder or .ms file..."); - const picked = await TauriService.pickFile( - "Select saveData.ms or GameHDD folder", - ["*.ms", "*"], - ); - if (!picked) { - setIsImporting(false); - return; - } - inputPath = picked; - worldName = picked.endsWith(".ms") - ? deriveWorldName(picked) - : deriveWorldName(picked); - setStatus("Copying LCE save..."); + setStatus("Selecting LCE save folder or .ms file..."); + const picked = await TauriService.pickFile( + "Select saveData.ms or GameHDD folder", + ["*.ms", "*"], + ); + if (!picked) { + setIsImporting(false); + return; } + const worldName = deriveWorldName(picked); + setStatus("Copying LCE save..."); const instancePath = await TauriService.getInstancePath(targetInstanceId); const saveDir = `${instancePath}/Windows64/GameHDD/${worldName}`; - if (worldType === "windows64") { - await TauriService.importWorld(inputPath, `${saveDir}/saveData.ms`); - } else { - await TauriService.importWorld(inputPath, `${saveDir}/saveData.ms`); - } + await TauriService.importWorld(picked, `${saveDir}/saveData.ms`); setStatus(`World imported into "${targetInstanceName}"!`); setTimeout(() => { @@ -129,25 +69,6 @@ export default function ImportWorldModal({ playBackSound(); if (isImporting) return; onClose(); - } else if ( - e.key === "ArrowDown" || - e.key === "ArrowUp" || - e.key === "Tab" - ) { - e.preventDefault(); - const max = typeOptions.length + 1; - setFocusIndex((prev) => { - if (e.key === "ArrowUp") return (prev - 1 + max) % max; - return (prev + 1) % max; - }); - } else if (e.key === "Enter") { - if (focusIndex < typeOptions.length) { - const opt = typeOptions[focusIndex]; - setWorldType(opt.id); - playPressSound(); - } else { - onClose(); - } } }; @@ -155,74 +76,16 @@ export default function ImportWorldModal({ if (!isOpen) return; window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); - }, [isOpen, focusIndex, worldType, isImporting]); + }, [isOpen, isImporting]); if (!isOpen) return null; - if (worldType && !isImporting) { - return ( - -
-

- Import {typeOptions.find((t) => t.id === worldType)?.label} -

-

- Destination:{" "} - {targetInstanceName} -

-

- {typeOptions.find((t) => t.id === worldType)?.desc} -

- -
- - -
-
-
- ); - } return (
{targetInstanceName}

- What type of world are you importing? + Select an existing LCE save (.ms file or GameHDD folder)

-
- {typeOptions.map((opt, i) => { - const isSelected = worldType === opt.id; - const isFocused = focusIndex === i; - return ( -
{ - playPressSound(); - setWorldType(opt.id); - }} - onMouseEnter={() => setFocusIndex(i)} - className={`w-full px-4 py-3 cursor-pointer flex flex-col gap-0.5 transition-all outline-none border-none ${ - isSelected - ? "bg-white/15 border-l-4 border-[#FFFF55]" - : isFocused - ? "bg-white/10 border-l-4 border-[#FFFF55]" - : "bg-black/20 hover:bg-black/30 border-l-4 border-transparent" - }`} - style={{ imageRendering: "pixelated" }} - > -
-
- {isSelected && ( -
- )} -
- - {opt.label} - -
-

{opt.desc}

-
- ); - })} -
- {error && (
{error} @@ -294,27 +116,30 @@ export default function ImportWorldModal({
+
) : ( diff --git a/src/services/RpcService.ts b/src/services/RpcService.ts index a63b75b..24c9de0 100644 --- a/src/services/RpcService.ts +++ b/src/services/RpcService.ts @@ -48,7 +48,7 @@ class RPC { activity.setDetails(details); activity.setState(state); activity.setActivity(ActivityType.Playing); - activity.setParty(new Party(`emerald_${username}`, [1, 2])); + if (!state.startsWith("Logged")) {activity.setParty(new Party(`emerald_${username}`, [1, 2]));} const assets = new Assets(); assets.setLargeImage("logo"); assets.setLargeText("LCE Emerald Launcher"); diff --git a/src/services/TauriService.ts b/src/services/TauriService.ts index 0a08f1f..e893906 100644 --- a/src/services/TauriService.ts +++ b/src/services/TauriService.ts @@ -294,14 +294,7 @@ export class TauriService { static async importWorld( inputPath: string, outputPath: string, - profile?: string, - preserveEntities?: boolean, ): Promise { - return invoke("import_world", { - inputPath, - outputPath, - profile: profile ?? null, - preserveEntities: preserveEntities ?? null, - }); + return invoke("import_world", { inputPath, outputPath }); } }