chore: initial commit
This commit is contained in:
commit
27162aa317
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "sestring"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Anna Clemens <sestring-crate@annaclemens.io>"]
|
||||||
|
description = "SeString parser/encoder for FFXIV-related purposes."
|
||||||
|
license = "EUPL-1.2"
|
||||||
|
categories = ["ffxiv"]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1"
|
||||||
|
thiserror = "1"
|
||||||
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
lazy_static = "1"
|
||||||
|
paste = "1"
|
|
@ -0,0 +1,18 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
use std::backtrace::Backtrace;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("io error")]
|
||||||
|
Io {
|
||||||
|
#[from]
|
||||||
|
error: std::io::Error,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
#[error("string was not valid utf-8")]
|
||||||
|
Utf8 {
|
||||||
|
#[from]
|
||||||
|
error: std::string::FromUtf8Error,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#![feature(backtrace)]
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod payload;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod serde;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
error::Error,
|
||||||
|
payload::Payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct SeString(Vec<Payload>);
|
||||||
|
|
||||||
|
impl SeString {
|
||||||
|
pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<Self, Error> {
|
||||||
|
let payloads = Payload::parse(bytes)?;
|
||||||
|
Ok(Self(payloads))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> Vec<u8> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| p.encode())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self) -> String {
|
||||||
|
self.0.iter()
|
||||||
|
.flat_map(|payload| {
|
||||||
|
match payload {
|
||||||
|
Payload::Text(t) => Some(&*t.0),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,300 @@
|
||||||
|
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, 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::{
|
||||||
|
io::{Read, Seek},
|
||||||
|
iter::once,
|
||||||
|
};
|
||||||
|
use byteorder::ReadBytesExt;
|
||||||
|
use crate::payload::SeStringChunkKind;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct AutoTranslatePayload {
|
||||||
|
pub group: u8,
|
||||||
|
pub key: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for AutoTranslatePayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, _chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
let group = reader.read_u8().map_err(Error::from)?;
|
||||||
|
let key = Self::read_integer(reader)?;
|
||||||
|
Ok(Self {
|
||||||
|
group,
|
||||||
|
key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for AutoTranslatePayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
let key = Self::make_integer(self.key);
|
||||||
|
let chunk_len = key.len() + 2;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::AutoTranslate.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(self.group))
|
||||||
|
.chain(key.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use byteorder::ReadBytesExt;
|
||||||
|
use crate::payload::{SeStringChunkKind, SeInteractableKind};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct DalamudLinkPayload {
|
||||||
|
pub plugin: String,
|
||||||
|
pub command: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for DalamudLinkPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, _chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
let plugin_len = reader.read_u8().map_err(Error::from)?;
|
||||||
|
let mut plugin_bytes = vec![0; plugin_len as usize];
|
||||||
|
reader.read_exact(&mut plugin_bytes).map_err(Error::from)?;
|
||||||
|
let plugin = String::from_utf8(plugin_bytes).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let command = Self::read_integer(reader)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
plugin,
|
||||||
|
command,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for DalamudLinkPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let plugin = self.plugin.as_bytes();
|
||||||
|
let command = Self::make_integer(self.command);
|
||||||
|
let chunk_len = 3 + plugin.len() + command.len();
|
||||||
|
|
||||||
|
// FIXME: check if chunk_len > 255
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::DalamudLink.as_u8()))
|
||||||
|
.chain(once(plugin.len() as u8))
|
||||||
|
.chain(plugin.iter().copied())
|
||||||
|
.chain(command.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct EmphasisPayload(pub bool);
|
||||||
|
|
||||||
|
impl EmphasisPayload {
|
||||||
|
pub fn enable() -> Self {
|
||||||
|
Self(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable() -> Self {
|
||||||
|
Self(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enabled(&self) -> bool {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for EmphasisPayload {
|
||||||
|
fn from(enabled: bool) -> Self {
|
||||||
|
Self(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for EmphasisPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let enabled = Self::read_integer(reader)?;
|
||||||
|
Ok(Self(enabled == 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for EmphasisPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let enabled = Self::make_integer(if self.0 { 1 } else { 0 });
|
||||||
|
let chunk_len = enabled.len() + 1;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Emphasis.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(enabled.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct IconPayload(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for IconPayload {
|
||||||
|
fn from(icon: u32) -> Self {
|
||||||
|
Self(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for IconPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let icon = Self::read_integer(reader)?;
|
||||||
|
Ok(Self(icon))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for IconPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let index = Self::make_integer(self.0);
|
||||||
|
let chunk_len = index.len() + 1;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Icon.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(index.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use crate::payload::{SeStringChunkKind, SeInteractableKind};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct ItemPayload {
|
||||||
|
pub id: u32,
|
||||||
|
pub hq: bool,
|
||||||
|
pub name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemPayload {
|
||||||
|
const HQ_THRESHOLD: u32 = 1_000_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for ItemPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
let mut id = Self::read_integer(&mut reader)?;
|
||||||
|
|
||||||
|
let hq = id > Self::HQ_THRESHOLD;
|
||||||
|
if hq {
|
||||||
|
id -= Self::HQ_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = if reader.stream_position().map_err(Error::from)? + 3 < chunk_len as u64 {
|
||||||
|
reader.seek(SeekFrom::Current(3)).map_err(Error::from)?; // unk
|
||||||
|
|
||||||
|
let name_len = Self::read_integer(&mut reader)?;
|
||||||
|
let mut name_bytes = vec![0; name_len as usize];
|
||||||
|
reader.read_exact(&mut name_bytes).map_err(Error::from)?;
|
||||||
|
|
||||||
|
Some(String::from_utf8(name_bytes).map_err(Error::from)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
hq,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for ItemPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let actual_id = if self.hq { self.id + Self::HQ_THRESHOLD } else { self.id };
|
||||||
|
let id = Self::make_integer(actual_id);
|
||||||
|
let name_bytes = self.name.as_ref().map(|x| x.as_bytes());
|
||||||
|
let mut chunk_len = 4 + id.len();
|
||||||
|
if let Some(name_bytes) = &name_bytes {
|
||||||
|
chunk_len += 1 + 1 + name_bytes.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_chain: Vec<u8> = match name_bytes {
|
||||||
|
Some(bs) => once(0xFF)
|
||||||
|
.chain(once((bs.len() + 1) as u8))
|
||||||
|
.chain(bs.iter().copied())
|
||||||
|
.collect(),
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::ItemLink.as_u8()))
|
||||||
|
.chain(id.into_iter())
|
||||||
|
.chain(once(0x02))
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(name_chain.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use crate::payload::{SeStringChunkKind, SeInteractableKind};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct MapLinkPayload {
|
||||||
|
pub territory_type: u32,
|
||||||
|
pub map: u32,
|
||||||
|
pub raw_x: i32,
|
||||||
|
pub raw_y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for MapLinkPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, _chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
let (territory_type, map) = Self::read_packed_integers(&mut reader)?;
|
||||||
|
let raw_x = Self::read_integer(&mut reader)? as i32;
|
||||||
|
let raw_y = Self::read_integer(&mut reader)? as i32;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Current(2)).map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
territory_type,
|
||||||
|
map,
|
||||||
|
raw_x,
|
||||||
|
raw_y,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for MapLinkPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let territory_map_packed = Self::make_packed_integers(self.territory_type, self.map);
|
||||||
|
let x = Self::make_integer(self.raw_x as u32);
|
||||||
|
let y = Self::make_integer(self.raw_y as u32);
|
||||||
|
let chunk_len = 4 + territory_map_packed.len() + x.len() + y.len();
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::MapPositionLink.as_u8()))
|
||||||
|
.chain(territory_map_packed.into_iter())
|
||||||
|
.chain(x.into_iter())
|
||||||
|
.chain(y.into_iter())
|
||||||
|
.chain(once(0xFF))
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::payload::{Encode, SeStringChunkKind};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct NewLinePayload;
|
||||||
|
|
||||||
|
impl Encode for NewLinePayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
vec![
|
||||||
|
Payload::START_BYTE,
|
||||||
|
SeStringChunkKind::NewLine.as_u8(),
|
||||||
|
0x01,
|
||||||
|
Payload::END_BYTE,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use crate::payload::{SeStringChunkKind, SeInteractableKind};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct PlayerPayload {
|
||||||
|
pub server_id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for PlayerPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, _chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
reader.seek(SeekFrom::Current(1)).map_err(Error::from)?; // unk
|
||||||
|
|
||||||
|
let server_id = Self::read_integer(&mut reader)?;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Current(2)).map_err(Error::from)?; // unk
|
||||||
|
|
||||||
|
let name_len = Self::read_integer(&mut reader)?;
|
||||||
|
let mut name_bytes = vec![0; name_len as usize];
|
||||||
|
reader.read_exact(&mut name_bytes).map_err(Error::from)?;
|
||||||
|
let name = String::from_utf8(name_bytes).map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server_id,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for PlayerPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let name = self.name.as_bytes();
|
||||||
|
let name_len = Self::make_integer(name.len() as u32);
|
||||||
|
let server = Self::make_integer(self.server_id);
|
||||||
|
let chunk_len = name.len() + server.len() + name_len.len() + 5;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::PlayerName.as_u8()))
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(server.into_iter())
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(0xFF))
|
||||||
|
.chain(name_len.into_iter())
|
||||||
|
.chain(name.iter().copied())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind, SeInteractableKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct QuestPayload(pub u32);
|
||||||
|
|
||||||
|
impl QuestPayload {
|
||||||
|
const OFFSET: u32 = 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for QuestPayload {
|
||||||
|
fn from(id: u32) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for QuestPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let id = Self::read_integer(reader)? + Self::OFFSET;
|
||||||
|
Ok(Self(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for QuestPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let id = Self::make_integer(self.0 - Self::OFFSET);
|
||||||
|
let chunk_len = id.len() + 4;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::QuestLink.as_u8()))
|
||||||
|
.chain(id.into_iter())
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::{Error, payload::{Decode, Encode}, Payload};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct RawPayload(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for RawPayload {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for RawPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, chunk_len: usize) -> Result<Self, Error> {
|
||||||
|
let mut data = vec![0; chunk_len];
|
||||||
|
reader.read_exact(&mut data).map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(Self(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for RawPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(self.0.iter().copied())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::payload::{Encode, SeStringChunkKind};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct SeHyphenPayload;
|
||||||
|
|
||||||
|
impl Encode for SeHyphenPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
vec![
|
||||||
|
Payload::START_BYTE,
|
||||||
|
SeStringChunkKind::SeHyphen.as_u8(),
|
||||||
|
0x01,
|
||||||
|
Payload::END_BYTE,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind, SeInteractableKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct StatusPayload(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for StatusPayload {
|
||||||
|
fn from(id: u32) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for StatusPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let id = Self::read_integer(reader)?;
|
||||||
|
Ok(Self(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for StatusPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let id = Self::make_integer(self.0);
|
||||||
|
let chunk_len = id.len() + 7;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::Interactable.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(once(SeInteractableKind::Status.as_u8()))
|
||||||
|
.chain(id.into_iter())
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(0x01))
|
||||||
|
.chain(once(0xFF))
|
||||||
|
.chain(once(0x02))
|
||||||
|
.chain(once(0x20))
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::{
|
||||||
|
Error,
|
||||||
|
payload::{Decode, Encode},
|
||||||
|
};
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct TextPayload(pub String);
|
||||||
|
|
||||||
|
impl<T: Into<String>> From<T> for TextPayload {
|
||||||
|
fn from(text: T) -> Self {
|
||||||
|
Self(text.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for TextPayload {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for TextPayload {
|
||||||
|
fn decode<R: Read + Seek>(mut reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let mut text = Vec::with_capacity(32);
|
||||||
|
|
||||||
|
let mut buf = [0; 32];
|
||||||
|
let mut read: usize;
|
||||||
|
loop {
|
||||||
|
read = reader.read(&mut buf).map_err(Error::from)?;
|
||||||
|
// end of stream
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = buf[..read].iter().position(|&b| b == Payload::START_BYTE);
|
||||||
|
if let Some(start) = start {
|
||||||
|
// 5 4 3 5 4 5 = 6 bytes read
|
||||||
|
// ^ - start byte at idx 2
|
||||||
|
// seek backwards until start byte is next to read: read - idx = 4 bytes
|
||||||
|
let offset = (read - start) as i64;
|
||||||
|
reader.seek(SeekFrom::Current(-offset)).map_err(Error::from)?;
|
||||||
|
|
||||||
|
// tell the rest of the loop that we only read up to the start byte
|
||||||
|
read = start as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &byte in &buf[..read] {
|
||||||
|
text.push(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we encountered a real payload, so break now
|
||||||
|
if start.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = if text.len() > 0 {
|
||||||
|
String::from_utf8(text).map_err(Error::from)?
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for TextPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
self.0.as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct UiForegroundPayload(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for UiForegroundPayload {
|
||||||
|
fn from(colour: u32) -> Self {
|
||||||
|
Self(colour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for UiForegroundPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let colour = Self::read_integer(reader)?;
|
||||||
|
Ok(Self(colour))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for UiForegroundPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let colour = Self::make_integer(self.0);
|
||||||
|
let chunk_len = colour.len() + 1;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::UiForeground.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(colour.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::payload::{Decode, Encode, SeStringChunkKind};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct UiGlowPayload(pub u32);
|
||||||
|
|
||||||
|
impl From<u32> for UiGlowPayload {
|
||||||
|
fn from(colour: u32) -> Self {
|
||||||
|
Self(colour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for UiGlowPayload {
|
||||||
|
fn decode<R: Read + Seek>(reader: R, _chunk_len: usize) -> Result<Self, crate::Error> {
|
||||||
|
let colour = Self::read_integer(reader)?;
|
||||||
|
Ok(Self(colour))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for UiGlowPayload {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let colour = Self::make_integer(self.0);
|
||||||
|
let chunk_len = colour.len() + 1;
|
||||||
|
|
||||||
|
once(Payload::START_BYTE)
|
||||||
|
.chain(once(SeStringChunkKind::UiGlow.as_u8()))
|
||||||
|
.chain(once(chunk_len as u8))
|
||||||
|
.chain(colour.into_iter())
|
||||||
|
.chain(once(Payload::END_BYTE))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||||
|
use crate::SeString;
|
||||||
|
|
||||||
|
impl Serialize for SeString {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer
|
||||||
|
{
|
||||||
|
self.encode().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SeString {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
let bytes = Vec::<u8>::deserialize(deserializer)?;
|
||||||
|
SeString::parse(&bytes).map_err(|e| serde::de::Error::custom(format!("invalid sestring: {:?}", e)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::AutoTranslatePayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
auto_translate {
|
||||||
|
payload = &[2, 46, 5, 5, 242, 2, 12, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::AutoTranslate(AutoTranslatePayload {
|
||||||
|
group: 5,
|
||||||
|
key: 524,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::{DalamudLinkPayload, RawPayload, TextPayload};
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
dalamud_link {
|
||||||
|
payload = &[2, 39, 22, 15, 18, 83, 105, 109, 112, 108, 101, 84, 119, 101, 97, 107, 115, 80, 108, 117, 103, 105, 110, 2, 3, 104, 116, 116, 112, 115, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 2, 39, 7, 207, 1, 1, 1, 255, 1, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::DalamudLink(DalamudLinkPayload {
|
||||||
|
plugin: "SimpleTweaksPlugin".to_string(),
|
||||||
|
command: 1,
|
||||||
|
}),
|
||||||
|
Payload::Text(TextPayload::from("https://example.com/")),
|
||||||
|
Payload::Raw(RawPayload(vec![39, 7, 207, 1, 1, 1, 255, 1])),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::{EmphasisPayload, TextPayload};
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
emphasis {
|
||||||
|
payload = &[78, 111, 119, 32, 112, 108, 97, 121, 105, 110, 103, 32, 2, 26, 2, 2, 3, 87, 97, 105, 108, 101, 114, 115, 32, 97, 110, 100, 32, 87, 97, 116, 101, 114, 119, 104, 101, 101, 108, 115, 2, 26, 2, 1, 3, 46];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Text(TextPayload::from("Now playing ")),
|
||||||
|
Payload::Emphasis(EmphasisPayload::enable()),
|
||||||
|
Payload::Text(TextPayload::from("Wailers and Waterwheels")),
|
||||||
|
Payload::Emphasis(EmphasisPayload::disable()),
|
||||||
|
Payload::Text(TextPayload::from(".")),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::IconPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
icon {
|
||||||
|
payload = &[2, 18, 2, 67, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Icon(IconPayload::from(66)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::ItemPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
item {
|
||||||
|
payload = &[2, 39, 7, 3, 242, 19, 9, 2, 1, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Item(ItemPayload {
|
||||||
|
id: 4873,
|
||||||
|
hq: false,
|
||||||
|
name: None,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::MapLinkPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
map {
|
||||||
|
payload = &[2, 39, 15, 4, 244, 132, 2, 246, 1, 16, 42, 246, 1, 130, 161, 255, 1, 3];
|
||||||
|
expected = SeString(vec!{
|
||||||
|
Payload::MapLink(MapLinkPayload {
|
||||||
|
territory_type: 132,
|
||||||
|
map: 2,
|
||||||
|
raw_x: 69674,
|
||||||
|
// x: 12.644841
|
||||||
|
raw_y: 98977,
|
||||||
|
// y: 13.231473
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
mod auto_translate;
|
||||||
|
mod dalamud_link;
|
||||||
|
mod emphasis;
|
||||||
|
mod icon;
|
||||||
|
mod item;
|
||||||
|
mod map_link;
|
||||||
|
mod new_line;
|
||||||
|
mod player;
|
||||||
|
mod quest;
|
||||||
|
mod raw;
|
||||||
|
mod se_hyphen;
|
||||||
|
mod status;
|
||||||
|
mod text;
|
||||||
|
mod ui_foreground;
|
||||||
|
mod ui_glow;
|
||||||
|
|
||||||
|
macro_rules! basic_tests {
|
||||||
|
($prefix:ident { payload = $payload:expr; expected = $expected:expr; }) => {
|
||||||
|
const PAYLOAD: &[u8] = $payload;
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref EXPECTED: crate::SeString = $expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::paste! {
|
||||||
|
#[test]
|
||||||
|
fn [<$prefix _parse>]() {
|
||||||
|
assert_eq!(
|
||||||
|
crate::SeString::parse(PAYLOAD).unwrap(),
|
||||||
|
*EXPECTED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn [<$prefix _encode>]() {
|
||||||
|
assert_eq!(
|
||||||
|
EXPECTED.encode(),
|
||||||
|
PAYLOAD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn [<$prefix _round_trip>]() {
|
||||||
|
assert_eq!(
|
||||||
|
crate::SeString::parse(PAYLOAD).unwrap().encode(),
|
||||||
|
PAYLOAD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use basic_tests;
|
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::{SeString, Payload};
|
||||||
|
use crate::payload::{TextPayload, NewLinePayload};
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
new_line {
|
||||||
|
payload = &[66, 101, 102, 111, 114, 101, 2, 16, 1, 3, 65, 102, 116, 101, 114];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Text(TextPayload::from("Before")),
|
||||||
|
Payload::NewLine(NewLinePayload),
|
||||||
|
Payload::Text(TextPayload::from("After")),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::PlayerPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
player {
|
||||||
|
payload = &[2, 39, 23, 1, 1, 74, 1, 255, 17, 69, 108, 111, 114, 97, 32, 65, 98, 121, 115, 115, 105, 110, 105, 97, 110, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Player(PlayerPayload {
|
||||||
|
server_id: 73,
|
||||||
|
name: "Elora Abyssinian".to_string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::QuestPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
quest {
|
||||||
|
payload = &[2, 39, 7, 5, 242, 2, 12, 1, 1, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Quest(QuestPayload::from(66060)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{SeString, Payload};
|
||||||
|
use crate::payload::RawPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
raw {
|
||||||
|
payload = &[2, 39, 7, 207, 1, 1, 1, 255, 1, 3];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Raw(RawPayload(vec![39, 7, 207, 1, 1, 1, 255, 1])),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::{SeString, Payload};
|
||||||
|
use crate::payload::{TextPayload, SeHyphenPayload};
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
se_hyphen {
|
||||||
|
payload = &[66, 101, 102, 111, 114, 101, 2, 31, 1, 3, 65, 102, 116, 101, 114];
|
||||||
|
expected = SeString(vec![
|
||||||
|
Payload::Text(TextPayload::from("Before")),
|
||||||
|
Payload::SeHyphen(SeHyphenPayload),
|
||||||
|
Payload::Text(TextPayload::from("After")),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::StatusPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
status {
|
||||||
|
payload = &[2, 39, 10, 9, 242, 1, 41, 1, 1, 255, 2, 32, 3];
|
||||||
|
expected = SeString(vec!{
|
||||||
|
Payload::Status(StatusPayload::from(297)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::TextPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
text {
|
||||||
|
payload = b"Hello, world!";
|
||||||
|
expected = SeString(vec!{
|
||||||
|
Payload::Text(TextPayload::from("Hello, world!")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::UiForegroundPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
ui_foreground {
|
||||||
|
payload = &[2, 72, 4, 242, 1, 244, 3];
|
||||||
|
expected = SeString(vec!{
|
||||||
|
Payload::UiForeground(UiForegroundPayload::from(500)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::{Payload, SeString};
|
||||||
|
use crate::payload::UiGlowPayload;
|
||||||
|
|
||||||
|
super::basic_tests! {
|
||||||
|
ui_glow {
|
||||||
|
payload = &[2, 73, 4, 242, 1, 245, 3];
|
||||||
|
expected = SeString(vec!{
|
||||||
|
Payload::UiGlow(UiGlowPayload::from(501)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue