301 lines
9.6 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|