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