sestring/src/payload.rs

301 lines
9.6 KiB
Rust

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<B: AsRef<[u8]>>(bytes: B) -> Result<Vec<Self>, 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<R: Read + Seek>(mut reader: R) -> Result<Self, Error> {
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<u8> {
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<u8>;
fn make_integer(int: u32) -> Vec<u8> {
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<u8> {
let value = (high << 16) | (low & 0xFF);
Self::make_integer(value)
}
}
trait Decode {
fn decode<R: Read + Seek>(reader: R, chunk_len: usize) -> Result<Self, crate::Error>
where Self: Sized;
fn read_integer<R: Read>(mut reader: R) -> Result<u32, crate::Error> {
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<R: Read>(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,
}
}
}