diff --git a/Cargo.lock b/Cargo.lock index 4e7f9f1a..00528bde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2684,6 +2684,7 @@ name = "ragnarok_bytes" version = "0.1.0" dependencies = [ "cgmath", + "encoding_rs", "ragnarok_procedural", ] diff --git a/korangar/src/inventory/skills.rs b/korangar/src/inventory/skills.rs index 1e0aad2e..e611ba02 100644 --- a/korangar/src/inventory/skills.rs +++ b/korangar/src/inventory/skills.rs @@ -33,7 +33,7 @@ impl SkillTree { let skills = skill_data .into_iter() .map(|skill_data| { - let file_path = format!("¾ÆÀÌÅÛ\\{}", skill_data.skill_name); + let file_path = format!("아이템\\{}", skill_data.skill_name); let sprite = sprite_loader.get_or_load(&format!("{file_path}.spr")).unwrap(); let actions = action_loader.get_or_load(&format!("{file_path}.act")).unwrap(); diff --git a/korangar/src/loaders/map/mod.rs b/korangar/src/loaders/map/mod.rs index bf371d70..da07fa42 100644 --- a/korangar/src/loaders/map/mod.rs +++ b/korangar/src/loaders/map/mod.rs @@ -315,7 +315,7 @@ fn parse_generic_data(resource_file: &str, game_file_loader: &G fn get_water_texture_paths(water_type: i32) -> Vec { let mut paths = Vec::with_capacity(32); for i in 0..32 { - let filename = format!("¿öÅÍ\\water{}{:02}.jpg", water_type, i); + let filename = format!("워터\\water{}{:02}.jpg", water_type, i); paths.push(filename); } paths diff --git a/korangar/src/loaders/server/mod.rs b/korangar/src/loaders/server/mod.rs index 4913672d..9b3f5e64 100644 --- a/korangar/src/loaders/server/mod.rs +++ b/korangar/src/loaders/server/mod.rs @@ -26,7 +26,7 @@ pub fn load_client_info(game_file_loader: &GameFileLoader) -> ClientInfo { let content = match get_xml_encoding(&client_info) { Some(encoding) => { - let (cow, ..) = encoding.decode(&client_info); + let (cow, _) = encoding.decode_without_bom_handling(&client_info); cow } None => String::from_utf8_lossy(client_info.as_slice()), diff --git a/korangar/src/main.rs b/korangar/src/main.rs index b87ce7c2..3378845d 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -107,7 +107,7 @@ const CLIENT_NAME: &str = "Korangar"; const ROLLING_CUTTER_ID: SkillId = SkillId(2036); const DEFAULT_MAP: &str = "geffen"; const DEFAULT_BACKGROUND_MUSIC: Option<&str> = Some("bgm\\01.mp3"); -const MAIN_MENU_CLICK_SOUND_EFFECT: &str = "¹öÆ°¼Ò¸®.wav"; +const MAIN_MENU_CLICK_SOUND_EFFECT: &str = "버튼소리.wav"; // TODO: The number of point lights that can cast shadows should be configurable // through the graphics settings. For now I just chose an arbitrary smaller // number that should be playable on most devices. diff --git a/korangar/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs index dd0128d7..5c79c8ac 100644 --- a/korangar/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -150,130 +150,130 @@ pub struct Common { #[allow(clippy::invisible_characters)] fn get_sprite_path_for_player_job(job_id: usize) -> &'static str { match job_id { - 0 => "Ãʺ¸ÀÚ", // NOVICE - 1 => "°Ë»Ç", // SWORDMAN - 2 => "À§Àúµå", // MAGICIAN - 3 => "±Ã¼Ö", // ARCHER - 4 => "¼ºÁ÷ÀÚ", // ACOLYTE - 5 => "»ÓÀÎ", // MERCHANT - 6 => "µµµÏ", // THIEF - 7 => "±â»ç", // KNIGHT - 8 => "¼ºÅõ»ç", // PRIEST - 9 => "¸¶¹Ý»Ç", // WIZARD - 10 => "Á¦Ã¶°ø", // BLACKSMITH - 11 => "ÇåÅÍ", // HUNTER - 12 => "¾î¼¼½Å", // ASSASSIN - 13 => "¿£´ë¿î", // CHICKEN - 14 => "Å©·ç¼¼ÀÌ´õ", // CRUSADER - 15 => "¸ùÅ©", // MONK - 16 => "¼¼ÀÌÁö", // SAGE - 17 => "·Î±×", // ROGUE - 18 => "¿¬±Ý¼ú»ç", // ALCHEMIST - 19 => "¹Ùµå", // BARD - 20 => "¹«Èñ", // DANCER - 23 => "½´ÆÛ³ëºñ½º", // SUPERNOVICE - 24 => "°Ç³Ê", // GUNSLINGER - 25 => "´ÑÀÚ", // NINJA - 4001 => "Ãʺ¸ÀÚ", // NOVICE_H - 4002 => "°Ë»Ç", // SWORDMAN_H - 4003 => "À§Àúµå", // MAGICIAN_H - 4004 => "±Ã¼Ö", // ARCHER_H - 4005 => "¼ºÁ÷ÀÚ", // ACOLYTE_H - 4006 => "»ÓÀÎ", // MERCHANT_H - 4007 => "µµµÏ", // THIEF_H - 4008 => "·Îµå³ªÀÌÆ®", // KNIGHT_H - 4009 => "ÇÏÀÌÇÁ¸®", // PRIEST_H - 4010 => "ÇÏÀÌÀ§Àúµå", // WIZARD_H - 4011 => "È­ÀÌÆ®½º¹Ì½º", // BLACKSMITH_H - 4012 => "½º³ªÀÌÆÛ", // HUNTER_H - 4013 => "¾î½Ø½ÅÅ©·Î½º", // ASSASSIN_H - 4014 => "¿£´ë¿î", // CHICKEN_H - 4015 => "Å©·ç¼¼ÀÌ´õ", // CRUSADER_H - 4016 => "¸ùÅ©", // MONK_H - 4017 => "¼¼ÀÌÁö", // SAGE_H - 4018 => "·Î±×", // ROGUE_H - 4019 => "¿¬±Ý¼ú»ç", // ALCHEMIST_H - 4020 => "¹Ùµå", // BARD_H - 4021 => "¹«Èñ", // DANCER_H - 4023 => "½´ÆÛ³ëºñ½º", // NOVICE_B - 4024 => "°Ë»Ç", // SWORDMAN_B - 4025 => "À§Àúµå", // MAGICIAN_B - 4026 => "±Ã¼Ö", // ARCHER_B - 4027 => "¼ºÁ÷ÀÚ", // ACOLYTE_B - 4028 => "»ÓÀÎ", // MERCHANT_B - 4029 => "µµµÏ", // THIEF_B - 4030 => "±â»ç", // KNIGHT_B - 4031 => "¼ºÅõ»ç", // PRIEST_B - 4032 => "¸¶¹Ý»Ç", // WIZARD_B - 4033 => "Á¦Ã¶°ø", // BLACKSMITH_B - 4034 => "ÇåÅÍ", // HUNTER_B - 4035 => "¾î¼¼½Å", // ASSASSIN_B - 4037 => "Å©·ç¼¼ÀÌ´õ", // CRUSADER_B - 4038 => "¸ùÅ©", // MONK_B - 4039 => "¼¼ÀÌÁö", // SAGE_B - 4040 => "·Î±×", // ROGUE_B - 4041 => "¿¬±Ý¼ú»ç", // ALCHEMIST_B - 4042 => "¹Ùµå", // BARD_B - 4043 => "¹«Èñ", // DANCER_B - 4045 => "½´ÆÛ³ëºñ½º", // SUPERNOVICE_B - 4054 => "·é³ªÀÌÆ®", // RUNE_KNIGHT - 4055 => "¿ö·Ï", // WARLOCK - 4056 => "·¹ÀÎÁ®", // RANGER - 4057 => "¾ÆÅ©ºñ¼ó", // ARCH_BISHOP - 4058 => "¹ÌÄÉ´Ð", // MECHANIC - 4059 => "±æ·Îƾũ·Î½º", // GUILLOTINE_CROSS - 4066 => "°¡µå", // ROYAL_GUARD - 4067 => "¼Ò¼­·¯", // SORCERER - 4068 => "¹Î½ºÆ®·²", // MINSTREL - 4069 => "¿ø´õ·¯", // WANDERER - 4070 => "½´¶ó", // SURA - 4071 => "Á¦³×¸¯", // GENETIC - 4072 => "½¦µµ¿ìüÀ̼­", // SHADOW_CHASER - 4060 => "·é³ªÀÌÆ®", // RUNE_KNIGHT_H - 4061 => "¿ö·Ï", // WARLOCK_H - 4062 => "·¹ÀÎÁ®", // RANGER_H - 4063 => "¾ÆÅ©ºñ¼ó", // ARCH_BISHOP_H - 4064 => "¹ÌÄÉ´Ð", // MECHANIC_H - 4065 => "±æ·Îƾũ·Î½º", // GUILLOTINE_CROSS_H - 4073 => "°¡µÅ", // ROYAL_GUARD_H - 4074 => "¼Ò¼­·¯", // SORCERER_H - 4075 => "¹Î½ºÆ®·²", // MINSTREL_H - 4076 => "¿ø´õ·¯", // WANDERER_H - 4077 => "½´¶ó", // SURA_H - 4078 => "Á¦³×¸¯", // GENETIC_H - 4079 => "½¦µµ¿ìüÀ̼­", // SHADOW_CHASER_H - 4096 => "·é³ªÀÌÆ®", // RUNE_KNIGHT_B - 4097 => "¿ö·Ï", // WARLOCK_B - 4098 => "·¹ÀÎÁ®", // RANGER_B - 4099 => "¾ÆÅ©ºñ¼ó", // ARCHBISHOP_B - 4100 => "¹ÌÄÉ´Ð", // MECHANIC_B - 4101 => "±æ·Îƾũ·Î½º", // GUILLOTINE_CROSS_B - 4102 => "°¡µå", // ROYAL_GUARD_B - 4103 => "¼Ò¼­·¯", // SORCERER_B - 4104 => "¹Î½ºÆ®·²", // MINSTREL_B - 4105 => "¿ø´õ·¯", // WANDERER_B - 4106 => "½´¶ó", // SURA_B - 4107 => "Á¦³×¸¯", // GENETIC_B - 4108 => "½¦µµ¿ìüÀ̼­", // SHADOW_CHASER_B - 4046 => "űǼҳâ", // TAEKWON - 4047 => "±Ç¼º", // STAR - 4049 => "¼Ò¿ï¸µÄ¿", // LINKER - 4190 => "½´ÆÛ³ëºñ½º", // SUPERNOVICE2 + 0 => "초보자", // NOVICE + 1 => "검사", // SWORDMAN + 2 => "마법사", // MAGICIAN + 3 => "궁수", // ARCHER + 4 => "성직자", // ACOLYTE + 5 => "상인", // MERCHANT + 6 => "도둑", // THIEF + 7 => "기사", // KNIGHT + 8 => "성투사", // PRIEST + 9 => "위저드", // WIZARD + 10 => "제철공", // BLACKSMITH + 11 => "헌터", // HUNTER + 12 => "어세신", // ASSASSIN + 13 => "페코페코_기사", // KNIGHT2 + 14 => "크루세이더", // CRUSADER + 15 => "몽크", // MONK + 16 => "세이지", // SAGE + 17 => "로그", // ROGUE + 18 => "연금술사", // ALCHEMIST + 19 => "바드", // BARD + 20 => "무희", // DANCER + 23 => "슈퍼노비스", // SUPERNOVICE + 24 => "건너", // GUNSLINGER + 25 => "닌자", // NINJA + 4001 => "초보자", // NOVICE_H + 4002 => "검사", // SWORDMAN_H + 4003 => "마법사", // MAGICIAN_H + 4004 => "궁수", // ARCHER_H + 4005 => "성직자", // ACOLYTE_H + 4006 => "상인", // MERCHANT_H + 4007 => "도둑", // THIEF_H + 4008 => "로드나이트", // KNIGHT_H + 4009 => "하이프리", // PRIEST_H + 4010 => "하이위저드", // WIZARD_H + 4011 => "화이트스미스", // BLACKSMITH_H + 4012 => "스나이퍼", // HUNTER_H + 4013 => "어쌔신크로스", // ASSASSIN_H + 4014 => "엔대운", // CHICKEN_H + 4015 => "크루세이더", // CRUSADER_H + 4016 => "몽크", // MONK_H + 4017 => "세이지", // SAGE_H + 4018 => "로그", // ROGUE_H + 4019 => "연금술사", // ALCHEMIST_H + 4020 => "바드", // BARD_H + 4021 => "무희", // DANCER_H + 4023 => "슈퍼노비스", // NOVICE_B + 4024 => "검뽀", // SWORDMAN_B + 4025 => "위저드", // MAGICIAN_B + 4026 => "궁솔", // ARCHER_B + 4027 => "성직자", // ACOLYTE_B + 4028 => "뿐인", // MERCHANT_B + 4029 => "도둑", // THIEF_B + 4030 => "기사", // KNIGHT_B + 4031 => "성투사", // PRIEST_B + 4032 => "마반뽀", // WIZARD_B + 4033 => "제철공", // BLACKSMITH_B + 4034 => "헌터", // HUNTER_B + 4035 => "어세신", // ASSASSIN_B + 4037 => "크루세이더", // CRUSADER_B + 4038 => "몽크", // MONK_B + 4039 => "세이지", // SAGE_B + 4040 => "로그", // ROGUE_B + 4041 => "연금술사", // ALCHEMIST_B + 4042 => "바드", // BARD_B + 4043 => "무희", // DANCER_B + 4045 => "슈퍼노비스", // SUPERNOVICE_B + 4054 => "룬나이트", // RUNE_KNIGHT + 4055 => "워록", // WARLOCK + 4056 => "레인져", // RANGER + 4057 => "아크비숍", // ARCH_BISHOP + 4058 => "미케닉", // MECHANIC + 4059 => "길로틴크로스", // GUILLOTINE_CROSS + 4066 => "가드", // ROYAL_GUARD + 4067 => "소서러", // SORCERER + 4068 => "민스트럴", // MINSTREL + 4069 => "원더러", // WANDERER + 4070 => "슈라", // SURA + 4071 => "제네릭", // GENETIC + 4072 => "쉐도우체이서", // SHADOW_CHASER + 4060 => "룬나이트", // RUNE_KNIGHT_H + 4061 => "워록", // WARLOCK_H + 4062 => "레인져", // RANGER_H + 4063 => "아크비숍", // ARCH_BISHOP_H + 4064 => "미케닉", // MECHANIC_H + 4065 => "길로틴크로스", // GUILLOTINE_CROSS_H + 4073 => "가돼", // ROYAL_GUARD_H + 4074 => "소서러", // SORCERER_H + 4075 => "민스트럴", // MINSTREL_H + 4076 => "원더러", // WANDERER_H + 4077 => "슈라", // SURA_H + 4078 => "제네릭", // GENETIC_H + 4079 => "쉐도우체이서", // SHADOW_CHASER_H + 4096 => "룬나이트", // RUNE_KNIGHT_B + 4097 => "워록", // WARLOCK_B + 4098 => "레인져", // RANGER_B + 4099 => "아크비숍", // ARCHBISHOP_B + 4100 => "미케닉", // MECHANIC_B + 4101 => "길로틴크로스", // GUILLOTINE_CROSS_B + 4102 => "가드", // ROYAL_GUARD_B + 4103 => "소서러", // SORCERER_B + 4104 => "민스트럴", // MINSTREL_B + 4105 => "원더러", // WANDERER_B + 4106 => "슈라", // SURA_B + 4107 => "제네릭", // GENETIC_B + 4108 => "쉐도우체이서", // SHADOW_CHASER_B + 4046 => "태권소년", // TAEKWON + 4047 => "권성", // STAR + 4049 => "소울링커", // LINKER + 4190 => "슈퍼노비스", // SUPERNOVICE2 4211 => "KAGEROU", // KAGEROU 4212 => "OBORO", // OBORO 4215 => "REBELLION", // REBELLION - 4222 => "´ÑÀÚ", // NINJA_B + 4222 => "닌자", // NINJA_B 4223 => "KAGEROU", // KAGEROU_B 4224 => "OBORO", // OBORO_B - 4225 => "űǼҳâ", // TAEKWON_B - 4226 => "±Ç¼º", // STAR_B - 4227 => "¼Ò¿ï¸µÄ¿", // LINKER_B - 4228 => "°Ç³Ê", // GUNSLINGER_B + 4225 => "태권소년", // TAEKWON_B + 4226 => "권성", // STAR_B + 4227 => "소울링커", // LINKER_B + 4228 => "건너", // GUNSLINGER_B 4229 => "REBELLION", // REBELLION_B - 4239 => "¼ºÁ¦", // STAR EMPEROR - 4240 => "¼Ò¿ï¸®ÆÛ", // SOUL REAPER - 4241 => "¼ºÁ¦", // STAR_EMPEROR_B - 4242 => "¼Ò¿ï¸®ÆÛ", // SOUL_REAPER_B + 4239 => "성제", // STAR EMPEROR + 4240 => "소울리퍼", // SOUL REAPER + 4241 => "성제", // STAR_EMPEROR_B + 4242 => "소울리퍼", // SOUL_REAPER_B 4252 => "DRAGON_KNIGHT", // DRAGON KNIGHT 4253 => "MEISTER", // MEISTER 4254 => "SHADOW_CROSS", // SHADOW CROSS @@ -293,19 +293,19 @@ fn get_sprite_path_for_player_job(job_id: usize) -> &'static str { 4305 => "SHIRANUI", // SHIRANUI 4306 => "NIGHT_WATCH", // NIGHT WATCH 4307 => "HYPER_NOVICE", // HYPER NOVICE - _ => "Ãʺ¸ÀÚ", // NOVICE + _ => "초보자", // NOVICE, } } fn get_entity_part_files(library: &Library, entity_type: EntityType, job_id: usize, sex: Sex, head: Option) -> Vec { let sex_sprite_path = match sex == Sex::Female { - true => "¿©", - false => "³²", + true => "여", + false => "남", }; fn player_body_path(sex_sprite_path: &str, job_id: usize) -> String { format!( - "Àΰ£Á·\\¸öÅë\\{}\\{}_{}", + "인간족\\몸통\\{}\\{}_{}", sex_sprite_path, get_sprite_path_for_player_job(job_id), sex_sprite_path @@ -313,7 +313,7 @@ fn get_entity_part_files(library: &Library, entity_type: EntityType, job_id: usi } fn player_head_path(sex_sprite_path: &str, head_id: usize) -> String { - format!("Àΰ£Á·\\¸Ó¸®Åë\\{}\\{}_{}", sex_sprite_path, head_id, sex_sprite_path) + format!("인간족\\머리통\\{}\\{}_{}", sex_sprite_path, head_id, sex_sprite_path) } let head_id = match (sex, head) { @@ -330,7 +330,7 @@ fn get_entity_part_files(library: &Library, entity_type: EntityType, job_id: usi player_head_path(sex_sprite_path, head_id), ], EntityType::Npc => vec![format!("npc\\{}", library.get_job_identity_from_id(job_id))], - EntityType::Monster => vec![format!("¸ó½ºÅÍ\\{}", library.get_job_identity_from_id(job_id))], + EntityType::Monster => vec![format!("몬스터\\{}", library.get_job_identity_from_id(job_id))], EntityType::Warp | EntityType::Hidden => vec![format!("npc\\{}", library.get_job_identity_from_id(job_id))], // TODO: change } } diff --git a/korangar/src/world/library/mod.rs b/korangar/src/world/library/mod.rs index d73c986f..612c6c74 100644 --- a/korangar/src/world/library/mod.rs +++ b/korangar/src/world/library/mod.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use encoding_rs::EUC_KR; use hashbrown::HashMap; use korangar_networking::{InventoryItem, NoMetadata, ShopItem}; use korangar_util::FileLoader; @@ -108,11 +109,12 @@ impl Library { if let Ok(table) = globals.get::("tbl") { for (item_id, item_table) in table.pairs::().flatten() { let info = ItemInfo { - identified_name: item_table.get("identifiedDisplayName").ok(), - unidentified_name: item_table.get("unidentifiedDisplayName").ok(), - identified_resource: item_table.get("identifiedResourceName").ok(), - unidentified_resource: item_table.get("unidentifiedResourceName").ok(), + identified_name: item_table.get("identifiedDisplayName").ok().map(fix_encoding), + unidentified_name: item_table.get("unidentifiedDisplayName").ok().map(fix_encoding), + identified_resource: item_table.get("identifiedResourceName").ok().map(fix_encoding), + unidentified_resource: item_table.get("unidentifiedResourceName").ok().map(fix_encoding), }; + result.insert(ItemId(item_id), info); } } @@ -142,7 +144,7 @@ impl Library { true => self.item_table.get(&item_id).and_then(|info| info.identified_resource.as_deref()), false => self.item_table.get(&item_id).and_then(|info| info.unidentified_resource.as_deref()), } - .unwrap_or("»ç°ú") + .unwrap_or("사과") // Apple } pub fn load_inventory_item_metadata( @@ -153,7 +155,7 @@ impl Library { let is_identified = item.is_identified(); let resource_name = self.get_item_resource_from_id(item.item_id, is_identified); - let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); + let full_path = format!("유저인터페이스\\item\\{resource_name}.bmp"); let texture = async_loader.request_item_sprite_load(ItemLocation::Inventory, item.item_id, &full_path, ImageType::Color); let name = self.get_item_name_from_id(item.item_id, is_identified).to_string(); @@ -164,7 +166,7 @@ impl Library { pub fn load_shop_item_metadata(&self, async_loader: &AsyncLoader, item: ShopItem) -> ShopItem { let resource_name = self.get_item_resource_from_id(item.item_id, true); - let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); + let full_path = format!("유저인터페이스\\item\\{resource_name}.bmp"); let texture = async_loader.request_item_sprite_load(ItemLocation::Shop, item.item_id, &full_path, ImageType::Color); let name = self.get_item_name_from_id(item.item_id, true).to_string(); @@ -173,3 +175,13 @@ impl Library { ShopItem { metadata, ..item } } } + +fn fix_encoding(broken: String) -> String { + let bytes: Vec = broken.chars().map(|char| char as u8).collect(); + let (char, error) = EUC_KR.decode_without_bom_handling(&bytes); + if error { + broken.to_string() + } else { + char.to_string() + } +} diff --git a/korangar/src/world/map/mod.rs b/korangar/src/world/map/mod.rs index a95e1a91..91801bfb 100644 --- a/korangar/src/world/map/mod.rs +++ b/korangar/src/world/map/mod.rs @@ -219,7 +219,7 @@ impl Map { }) } - // We want to make sure that the object set also caputres the lifetime of the + // We want to make sure that the object set also captures the lifetime of the // map, so we never have a stale object set. #[cfg_attr(feature = "debug", korangar_debug::profile)] pub fn cull_objects_in_sphere<'a>( diff --git a/korangar/src/world/particles/mod.rs b/korangar/src/world/particles/mod.rs index bba82003..4c01ea8d 100644 --- a/korangar/src/world/particles/mod.rs +++ b/korangar/src/world/particles/mod.rs @@ -108,7 +108,7 @@ impl QuestIcon { let effect_id = quest_effect.effect as usize; let texture = texture_loader .get_or_load( - &format!("À¯ÀúÀÎÅÍÆäÀ̽º\\minimap\\quest_{}_{}.bmp", effect_id, 1), /* 1 - 3 */ + &format!("유저인터페이스\\minimap\\quest_{}_{}.bmp", effect_id, 1), /* 1 - 3 */ ImageType::Color, ) .unwrap(); diff --git a/korangar_networking/src/lib.rs b/korangar_networking/src/lib.rs index 837e2ad9..7a4ed860 100644 --- a/korangar_networking/src/lib.rs +++ b/korangar_networking/src/lib.rs @@ -17,6 +17,7 @@ use event::{ CharacterServerDisconnectedEvent, DisconnectedEvent, LoginServerDisconnectedEvent, MapServerDisconnectedEvent, NetworkEventList, NoNetworkEvents, }; +use ragnarok_bytes::encoding::UTF_8; use ragnarok_bytes::{ByteReader, FromBytes}; use ragnarok_packets::handler::{DuplicateHandlerError, HandlerResult, NoPacketCallback, PacketCallback, PacketHandler}; use ragnarok_packets::*; @@ -320,6 +321,7 @@ where let data = &buffer[..cut_off_buffer_base + received_bytes]; let mut byte_reader = ByteReader::without_metadata(data); + byte_reader.set_encoding(UTF_8); if read_account_id { let account_id = AccountId::from_bytes(&mut byte_reader).unwrap(); diff --git a/ragnarok_bytes/Cargo.toml b/ragnarok_bytes/Cargo.toml index 7d98b857..fdbe5e88 100644 --- a/ragnarok_bytes/Cargo.toml +++ b/ragnarok_bytes/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" [dependencies] cgmath = { workspace = true, optional = true } +encoding_rs = { workspace = true } ragnarok_procedural = { workspace = true, optional = true } [features] -cgmath = [ "dep:cgmath" ] -derive = [ "ragnarok_procedural" ] +cgmath = ["dep:cgmath"] +derive = ["ragnarok_procedural"] diff --git a/ragnarok_bytes/src/from_bytes/implement.rs b/ragnarok_bytes/src/from_bytes/implement.rs index 7ecde434..8b65e2b4 100644 --- a/ragnarok_bytes/src/from_bytes/implement.rs +++ b/ragnarok_bytes/src/from_bytes/implement.rs @@ -65,19 +65,16 @@ impl FromBytes for [T; SIZE] { impl FromBytes for String { fn from_bytes(byte_reader: &mut ByteReader) -> ConversionResult { - let mut value = Vec::::new(); + let mut bytes = Vec::::new(); while let Ok(byte) = byte_reader.byte::() { match byte { 0 => break, - byte => value.push(byte), + byte => bytes.push(byte), } } - let result = String::from_utf8(value) - .map_err(|error| error.into_bytes()) - .unwrap_or_else(|error| error.iter().map(|byte| *byte as char).collect()); - Ok(result) + Ok(byte_reader.decode_string(&bytes)) } } diff --git a/ragnarok_bytes/src/lib.rs b/ragnarok_bytes/src/lib.rs index e453617a..53070c48 100644 --- a/ragnarok_bytes/src/lib.rs +++ b/ragnarok_bytes/src/lib.rs @@ -7,6 +7,7 @@ mod from_bytes; mod reader; mod to_bytes; +pub use encoding_rs as encoding; #[cfg(feature = "derive")] pub use ragnarok_procedural::{ByteConvertable, FixedByteSize, FromBytes, ToBytes}; diff --git a/ragnarok_bytes/src/reader.rs b/ragnarok_bytes/src/reader.rs index bca249a7..974cc39d 100644 --- a/ragnarok_bytes/src/reader.rs +++ b/ragnarok_bytes/src/reader.rs @@ -1,5 +1,7 @@ use std::any::TypeId; +use encoding_rs::{Encoding, EUC_KR}; + use crate::{ConversionError, ConversionErrorType, ConversionResult}; /// Saved state of a [`ByteReader`] that can be restored. @@ -20,6 +22,8 @@ pub(crate) struct TemporaryLimit { /// example a version). /// /// The reader is intended for reading data without lookahead. +/// The reader reads strings with the default encoding of "EUC-KR", which can be +/// changed by calling [`set_encoding`](ByteReader::set_encoding). /// /// The state of the reader can be saved at any time with /// [`create_save_point`](ByteReader::create_save_point), and restored with @@ -34,6 +38,7 @@ where Meta: 'static, { data: &'a [u8], + encoding: &'static Encoding, offset: usize, limit: usize, metadata: Meta, @@ -66,12 +71,27 @@ where Self { data, + encoding: EUC_KR, offset: 0, limit, metadata, } } + /// Sets the encoding used to decode strings. + pub fn set_encoding(&mut self, encoding: &'static Encoding) { + self.encoding = encoding; + } + + pub fn decode_string(&mut self, bytes: &[u8]) -> String { + let (cow, error) = self.encoding.decode_without_bom_handling(bytes); + if error { + bytes.iter().map(|byte| *byte as char).collect() + } else { + cow.to_string() + } + } + pub fn get_offset(&self) -> usize { self.offset }