pub mod auto_translate; pub mod dalamud_link; pub mod emphasis; pub mod icon; pub mod item; pub mod map_link; pub mod new_line; pub mod player; pub mod quest; pub mod raw; pub mod se_hyphen; pub mod status; pub mod text; pub mod ui_foreground; pub mod ui_glow; pub use self::{ auto_translate::AutoTranslatePayload, dalamud_link::DalamudLinkPayload, emphasis::EmphasisPayload, icon::IconPayload, item::ItemPayload, map_link::MapLinkPayload, new_line::NewLinePayload, player::PlayerPayload, quest::QuestPayload, raw::RawPayload, se_hyphen::SeHyphenPayload, status::StatusPayload, text::TextPayload, ui_foreground::UiForegroundPayload, ui_glow::UiGlowPayload, }; use crate::Error; use std::io::{Read, Cursor, Seek, SeekFrom}; #[derive(Debug, Clone, PartialEq)] pub enum Payload { AutoTranslate(AutoTranslatePayload), DalamudLink(DalamudLinkPayload), Emphasis(EmphasisPayload), Icon(IconPayload), Item(ItemPayload), MapLink(MapLinkPayload), NewLine(NewLinePayload), Player(PlayerPayload), Quest(QuestPayload), Raw(RawPayload), SeHyphen(SeHyphenPayload), Status(StatusPayload), Text(TextPayload), UiForeground(UiForegroundPayload), UiGlow(UiGlowPayload), } impl Payload { const START_BYTE: u8 = 0x02; const END_BYTE: u8 = 0x03; pub fn parse>(bytes: B) -> Result, Error> { let bytes = bytes.as_ref(); let num_bytes = bytes.len(); let mut cursor = Cursor::new(bytes); let mut payloads = Vec::with_capacity(1); while (cursor.position() as usize) < num_bytes { payloads.push(Self::read_one(&mut cursor)?); } Ok(payloads) } pub fn read_one(mut reader: R) -> Result { use byteorder::ReadBytesExt; let initial = reader.read_u8().map_err(Error::from)?; reader.seek(SeekFrom::Current(-1)).map_err(Error::from)?; if initial != Self::START_BYTE { // chunk len doesn't matter here TextPayload::decode(&mut reader, 0).map(Self::Text) } else { // seek back forward reader.seek(SeekFrom::Current(1)).map_err(Error::from)?; let post_start = reader.stream_position().map_err(Error::from)?; let chunk_type = reader.read_u8().map_err(Error::from)?; let chunk_len = TextPayload::read_integer(&mut reader)? as usize; let start = reader.stream_position().map_err(Error::from)?; let chunk = match SeStringChunkKind::from_u8(chunk_type) { SeStringChunkKind::Emphasis => EmphasisPayload::decode(&mut reader, chunk_len).map(Self::Emphasis)?, SeStringChunkKind::NewLine => Self::NewLine(NewLinePayload), SeStringChunkKind::SeHyphen => Self::SeHyphen(SeHyphenPayload), SeStringChunkKind::Interactable => { let sub_type = reader.read_u8().map_err(Error::from)?; match SeInteractableKind::from_u8(sub_type) { SeInteractableKind::PlayerName => PlayerPayload::decode(&mut reader, chunk_len).map(Self::Player)?, SeInteractableKind::ItemLink => ItemPayload::decode(&mut reader, chunk_len).map(Self::Item)?, SeInteractableKind::MapPositionLink => MapLinkPayload::decode(&mut reader, chunk_len).map(Self::MapLink)?, SeInteractableKind::QuestLink => QuestPayload::decode(&mut reader, chunk_len).map(Self::Quest)?, SeInteractableKind::Status => StatusPayload::decode(&mut reader, chunk_len).map(Self::Status)?, SeInteractableKind::DalamudLink => DalamudLinkPayload::decode(&mut reader, chunk_len).map(Self::DalamudLink)?, SeInteractableKind::LinkTerminator | SeInteractableKind::Unknown(_) => { reader.seek(SeekFrom::Start(post_start)).map_err(Error::from)?; let additional = start - post_start; RawPayload::decode(&mut reader, chunk_len + additional as usize).map(Self::Raw)? } } } SeStringChunkKind::AutoTranslate => AutoTranslatePayload::decode(&mut reader, chunk_len).map(Self::AutoTranslate)?, SeStringChunkKind::UiForeground => UiForegroundPayload::decode(&mut reader, chunk_len).map(Self::UiForeground)?, SeStringChunkKind::UiGlow => UiGlowPayload::decode(&mut reader, chunk_len).map(Self::UiGlow)?, SeStringChunkKind::Icon => IconPayload::decode(&mut reader, chunk_len).map(Self::Icon)?, SeStringChunkKind::Unknown(_) => { reader.seek(SeekFrom::Start(post_start)).map_err(Error::from)?; let additional = start - post_start; RawPayload::decode(&mut reader, chunk_len + additional as usize).map(Self::Raw)? }, }; // skip whatever's left let total_read = reader.stream_position().map_err(Error::from)? - start; // +1 for END_BYTE reader.seek(SeekFrom::Current((chunk_len as u64 - total_read + 1) as i64)).map_err(Error::from)?; Ok(chunk) } } pub fn encode(&self) -> Vec { match self { Payload::AutoTranslate(p) => p.encode(), Payload::DalamudLink(p) => p.encode(), Payload::Emphasis(p) => p.encode(), Payload::Icon(p) => p.encode(), Payload::Item(p) => p.encode(), Payload::MapLink(p) => p.encode(), Payload::NewLine(p) => p.encode(), Payload::Player(p) => p.encode(), Payload::Quest(p) => p.encode(), Payload::Raw(p) => p.encode(), Payload::SeHyphen(p) => p.encode(), Payload::Status(p) => p.encode(), Payload::Text(p) => p.encode(), Payload::UiForeground(p) => p.encode(), Payload::UiGlow(p) => p.encode(), } } } trait Encode { fn encode(&self) -> Vec; fn make_integer(int: u32) -> Vec { use byteorder::{ByteOrder, LittleEndian}; let mut vec = Vec::with_capacity(4); if int < 0xCF { vec.push(int as u8 + 1); return vec; } let mut bytes = [0; 4]; LittleEndian::write_u32(&mut bytes, int); vec.push(0xF0); for i in (0..4).rev() { if bytes[i] != 0 { vec.push(bytes[i]); vec[0] |= 1 << i; } } vec[0] -= 1; vec } fn make_packed_integers(high: u32, low: u32) -> Vec { let value = (high << 16) | (low & 0xFF); Self::make_integer(value) } } trait Decode { fn decode(reader: R, chunk_len: usize) -> Result where Self: Sized; fn read_integer(mut reader: R) -> Result { use byteorder::{ByteOrder, LittleEndian, ReadBytesExt}; let mut marker = reader.read_u8().map_err(Error::from)?; if marker < 0xD0 { return Ok((marker - 1) as u32); } marker = (marker + 1) & 0b1111; let mut ret = [0; 4]; for i in (0..4).rev() { ret[i] = if (marker & (1 << i)) == 0 { 0 } else { reader.read_u8().map_err(Error::from)? }; } Ok(LittleEndian::read_u32(&ret)) } fn read_packed_integers(reader: R) -> Result<(u32, u32), crate::Error> { let value = Self::read_integer(reader)?; Ok((value >> 16, value & 0xFFFF)) } } pub(crate) enum SeStringChunkKind { Icon, Emphasis, NewLine, SeHyphen, Interactable, AutoTranslate, UiForeground, UiGlow, Unknown(u8), } impl SeStringChunkKind { fn from_u8(byte: u8) -> Self { match byte { 0x12 => Self::Icon, 0x1A => Self::Emphasis, 0x10 => Self::NewLine, 0x1F => Self::SeHyphen, 0x27 => Self::Interactable, 0x2E => Self::AutoTranslate, 0x48 => Self::UiForeground, 0x49 => Self::UiGlow, x => Self::Unknown(x), } } pub(crate) fn as_u8(&self) -> u8 { match self { Self::Icon => 0x12, Self::Emphasis => 0x1A, Self::NewLine => 0x10, Self::SeHyphen => 0x1F, Self::Interactable => 0x27, Self::AutoTranslate => 0x2E, Self::UiForeground => 0x48, Self::UiGlow => 0x49, Self::Unknown(x) => *x, } } } enum SeInteractableKind { PlayerName, ItemLink, MapPositionLink, QuestLink, Status, DalamudLink, LinkTerminator, Unknown(u8), } impl SeInteractableKind { fn from_u8(byte: u8) -> Self { match byte { 0x01 => Self::PlayerName, 0x03 => Self::ItemLink, 0x04 => Self::MapPositionLink, 0x05 => Self::QuestLink, 0x09 => Self::Status, 0x0F => Self::DalamudLink, 0xCF => Self::LinkTerminator, x => Self::Unknown(x), } } fn as_u8(&self) -> u8 { match self { Self::PlayerName => 0x01, Self::ItemLink => 0x03, Self::MapPositionLink => 0x04, Self::QuestLink => 0x05, Self::Status => 0x09, Self::DalamudLink => 0x0F, Self::LinkTerminator => 0xCF, Self::Unknown(x) => *x, } } }