Add UiMessageServerToSingleClient game message

This commit is contained in:
lcdr
2020-12-31 18:39:52 +01:00
parent 92331a8893
commit aa450b4dfe
9 changed files with 497 additions and 7 deletions
-2
View File
@@ -132,7 +132,6 @@ fn parse(path: &Path) -> Res<usize> {
&& !file.name().contains("[4d-03]")
&& !file.name().contains("[6d-03]")
&& !file.name().contains("[91-03]")
&& !file.name().contains("[a0-04]")
&& !file.name().contains("[1a-05]")
&& !file.name().contains("[e6-05]")
&& !file.name().contains("[16-06]")
@@ -151,7 +150,6 @@ fn parse(path: &Path) -> Res<usize> {
&& !file.name().contains("[845]")
&& !file.name().contains("[877]")
&& !file.name().contains("[913]")
&& !file.name().contains("[1184]")
&& !file.name().contains("[1306]")
&& !file.name().contains("[1510]")
&& !file.name().contains("[1558]")
+1 -1
View File
@@ -69,7 +69,7 @@ fn gen_test_case(type_name: &Ident, test_params: &Option<Punctuated<NestedMeta,
let mut expected = include!(#rs_path);
let mut input = bin;
let mut reader = #reader_code;
let parsed: #type_name<#test_params> = ::endio::LERead::read(&mut reader).unwrap();
let parsed: #type_name<#test_params> = ::endio::LERead::read(&mut reader).expect("error while parsing bin");
let mut read_buf = [0];
let amount_read = std::io::Read::read(&mut reader, &mut read_buf).unwrap();
assert_eq!(amount_read, 0, "bin not fully read");
+73 -1
View File
@@ -5,6 +5,78 @@
#![feature(specialization)]
#![allow(incomplete_features)]
/**
Creates an [`Amf3::Array`](crate::world::amf3::Amf3::Array) containing the arguments.
Since AMF3 arrays are both vectors and maps at the same time, there are multiple forms of the macro, for map and for vector usage.
### Map usage
The syntax is `name: value`, where `name` is a string literal that will be converted to an [`Amf3String`](crate::world::amf3::Amf3String), and `value` is an expression that will be converted to an [`Amf3`] object.
Example:
```
# #[macro_use] extern crate lu_packets;
# use lu_packets::amf3;
# fn main() {
amf3! {
"false": false,
"true": true,
"double1": 3.14f32,
"double2": 3.14f64,
"string": "string",
"array": amf3! { "inner": "array"},
};
# }
```
### Vector usage
The syntax is the exact same as with the [`vec!`] macro, except that the arguments will be converted to an [`Amf3`] object before being inserted.
Example:
```
# #[macro_use] extern crate lu_packets;
# use lu_packets::amf3;
# fn main() {
amf3! [true, false, true];
amf3! [true; 4];
# }
```
[`Amf3`]: crate::world::amf3::Amf3
*/
#[macro_export]
macro_rules! amf3 {
{} => { $crate::world::amf3::Amf3::Array($crate::world::amf3::Amf3Array::new()) };
($($name:literal:$value:expr),+ $(,)?) => {
{
let mut array = $crate::world::amf3::Amf3Array::new();
$(array.map.insert(::std::convert::TryInto::try_into($name).unwrap(), ::std::convert::TryInto::try_into($value).unwrap());)*
$crate::world::amf3::Amf3::Array(array)
}
};
($value:expr; $n:expr) => {
{
let converted = ::std::convert::TryInto::try_into($value).unwrap();
let mut array = $crate::world::amf3::Amf3Array {
map: ::std::collections::HashMap::new(),
vec: vec![converted; $n],
};
$crate::world::amf3::Amf3::Array(array)
}
};
($($value:expr),+ $(,)?) => {
{
let mut array = $crate::world::amf3::Amf3Array::new();
$(array.vec.push(::std::convert::TryInto::try_into($value).unwrap());)*
$crate::world::amf3::Amf3::Array(array)
}
};
}
/**
Creates a [`LuNameValue`] containing the arguments.
@@ -33,7 +105,7 @@
Care should be taken with integer and float literals to suffix them with the correct type, as seen above. Rust assumes `i32` for integer and `f64` for float literals by default, which may not be what you want, and can lead to incorrect serialization.
[`LuNameValue`]: crate::world::LuNameValue
[`LuVarWString<u32>`]: crate::common::str::variable::LuVarWString
[`LuVarWString<u32>`]: crate::common::LuVarWString
[`LnvValue`]: crate::world::LnvValue
*/
#[macro_export]
+400
View File
@@ -0,0 +1,400 @@
//! (De-)serialization support for the [AMF3 format](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf).
use std::borrow::Borrow;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::io::{Error, ErrorKind::InvalidData, Read, Result as Res, Write};
use std::ops::{Index, IndexMut};
use endio::{Deserialize, LE, LERead, LEWrite, Serialize};
use lu_packets_derive::GmParam;
struct Amf3Reader<'a, R: Read> {
inner: &'a mut R,
string_ref_table: Vec<Amf3String>,
}
impl<R: Read> Read for Amf3Reader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> Res<usize> {
self.inner.read(buf)
}
}
struct Amf3Writer<'a, W: Write> {
inner: &'a mut W,
string_ref_table: Vec<Amf3String>,
}
impl<W: Write> Write for Amf3Writer<'_, W> {
fn write(&mut self, buf: &[u8]) -> Res<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> Res<()> {
self.inner.flush()
}
}
/// An error returned when an integer is too large to be represented by an `U29`.
#[derive(Debug)]
pub struct U29Error;
/// An unsigned integer whose serialized form has variable length, with a maximum value of 2^29-1. [`See spec section 1.3.1`](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf#%5B%7B%22num%22%3A18%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C88%2C643%2C0%5D).
#[derive(Debug)]
struct U29(u32);
/// Converts the value to a [`U29`] if it is less than 2^29, returns [`U29Error`] otherwise.
impl TryFrom<usize> for U29 {
type Error = U29Error;
fn try_from(value: usize) -> Result<Self, Self::Error> {
if value >= 1 << 29 {
Err(U29Error)
} else {
Ok(Self(value as u32))
}
}
}
impl<R: Read> Deserialize<LE, R> for U29 {
fn deserialize(reader: &mut R) -> Res<Self> {
let mut value = 0;
for _ in 0..3 {
let byte: u8 = LERead::read(reader)?;
let byte = byte as u32;
value = (value << 7) | (byte & 0x7f);
if byte & 0x80 == 0 {
return Ok(Self(value));
}
}
let byte: u8 = LERead::read(reader)?;
let byte = byte as u32;
value = (value << 8) | byte;
Ok(Self(value))
}
}
impl<'a, W: Write> Serialize<LE, W> for &'a U29 {
fn serialize(self, writer: &mut W) -> Res<()> {
let v = self.0;
if v <= 0x7f {
LEWrite::write(writer, v as u8)
} else if v <= 0x3fff {
LEWrite::write(writer, (v >> 7) as u8 | 0x80)?;
LEWrite::write(writer, v as u8 & 0x7f)
} else if v <= 0x1fffff {
LEWrite::write(writer, (v >> 14) as u8 | 0x80)?;
LEWrite::write(writer, (v >> 7 ) as u8 | 0x80)?;
LEWrite::write(writer, v as u8 & 0x7f)
} else {
LEWrite::write(writer, (v >> 22) as u8 | 0x80)?;
LEWrite::write(writer, (v >> 15) as u8 | 0x80)?;
LEWrite::write(writer, (v >> 8 ) as u8 | 0x80)?;
LEWrite::write(writer, v as u8)
}
}
}
/**
A string with a maximum length of 2^29-1.
[See spec section 3.8 for more](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf#%5B%7B%22num%22%3A22%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C88%2C196%2C0%5D).
*/
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Amf3String(String);
impl Debug for Amf3String {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(f)
}
}
impl Borrow<str> for Amf3String {
fn borrow(&self) -> &str {
&self.0[..]
}
}
impl TryFrom<&str> for Amf3String {
type Error = U29Error;
fn try_from(string: &str) -> Result<Self, Self::Error> {
if string.len() & (1 << 29) != 0 {
Err(U29Error)
} else {
Ok(Self(string.into()))
}
}
}
impl<R: Read> Deserialize<LE, Amf3Reader<'_, R>> for Amf3String {
fn deserialize(reader: &mut Amf3Reader<'_, R>) -> Res<Self> {
let value_and_is_inline: U29 = LERead::read(reader)?;
let is_inline = value_and_is_inline.0 & 0x01 == 1;
let value = value_and_is_inline.0 >> 1;
let string = if !is_inline {
let index = value;
match reader.string_ref_table.get(index as usize) {
Some(x) => x.0.clone(),
None => { return Err(Error::new(InvalidData, "invalid reference index")) }
}
} else {
let length = value;
let mut vec = vec![0u8; length as usize];
Read::read_exact(reader, &mut vec)?;
let string = match String::from_utf8(vec) {
Ok(x) => x,
Err(_) => { return Err(Error::new(InvalidData, "string is not valid utf8")) }
};
if string != "" {
reader.string_ref_table.push(Self(string.clone()));
}
string
};
Ok(Self(string))
}
}
impl<'a, W: Write> Serialize<LE, Amf3Writer<'_, W>> for &'a Amf3String {
fn serialize(self, writer: &mut Amf3Writer<'_, W>) -> Res<()> {
if self.0 == "" {
let length_and_is_inline = U29(1);
return LEWrite::write(writer, &length_and_is_inline);
}
match writer.string_ref_table.iter().position(|x| x == self) {
Some(index) => {
let index_and_is_inline = U29((index as u32) << 1);
LEWrite::write(writer, &index_and_is_inline)
}
None => {
let length_and_is_inline = U29((self.0.len() as u32) << 1 | 1);
LEWrite::write(writer, &length_and_is_inline)?;
writer.string_ref_table.push(Amf3String(self.0.clone()));
Write::write_all(writer, self.0.as_bytes())
}
}
}
}
/**
Both a dense and associative array at the same time.
[See spec section 3.11 for more](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf#%5B%7B%22num%22%3A24%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C88%2C720%2C0%5D).
*/
#[derive(Clone, PartialEq)]
pub struct Amf3Array {
pub map: HashMap<Amf3String, Amf3>,
pub vec: Vec<Amf3>,
}
impl Amf3Array {
pub fn new() -> Self {
Self { map: HashMap::new(), vec: vec![] }
}
}
impl Debug for Amf3Array {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let map_empty = self.map.is_empty();
let vec_empty = self.vec.is_empty();
if !map_empty && vec_empty {
write!(f, "amf3! ")?;
self.map.fmt(f)
} else if map_empty && !vec_empty {
write!(f, "amf3! ")?;
self.vec.fmt(f)
} else if map_empty && vec_empty {
write!(f, "amf3! {{}}")
} else {
write!(f, "Amf3Array {{ map: {:?}, vec: {:?} }}", self.map, self.vec)
}
}
}
impl Index<usize> for Amf3Array {
type Output = Amf3;
fn index(&self, index: usize) -> &Self::Output {
&self.vec[index]
}
}
impl IndexMut<usize> for Amf3Array {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.vec[index]
}
}
impl Index<&Amf3String> for Amf3Array {
type Output = Amf3;
fn index(&self, key: &Amf3String) -> &Amf3 {
&self.map[key]
}
}
impl Index<&str> for Amf3Array {
type Output = Amf3;
fn index(&self, key: &str) -> &Amf3 {
&self.map[key]
}
}
impl<R: Read> Deserialize<LE, Amf3Reader<'_, R>> for Amf3Array {
fn deserialize(reader: &mut Amf3Reader<'_, R>) -> Res<Self> {
let length_and_is_inline: U29 = LERead::read(reader)?;
let is_inline = length_and_is_inline.0 & 0x01 == 1;
let length = length_and_is_inline.0 >> 1;
if !is_inline { todo!() }
let mut map = HashMap::new();
loop {
let key: Amf3String = LERead::read(reader)?;
if key.0 == "" {
break;
}
let value = deser_amf3(reader)?;
map.insert(key, value);
}
let mut vec = Vec::with_capacity(length as usize);
for _ in 0..length {
let value = deser_amf3(reader)?;
vec.push(value);
}
Ok(Self { map, vec })
}
}
impl<'a, W: Write> Serialize<LE, Amf3Writer<'_, W>> for &'a Amf3Array {
fn serialize(self, writer: &mut Amf3Writer<'_, W>) -> Res<()> {
let length_and_is_inline = U29((self.vec.len() as u32) << 1 | 1);
LEWrite::write(writer, &length_and_is_inline)?;
#[cfg(test)]
let key_value = {
let mut key_value: Vec<_> = self.map.iter().collect();
key_value.sort_unstable_by(|(k1, _), (k2, _)| k1.cmp(k2));
key_value
};
#[cfg(not(test))]
let key_value = self.map.iter();
for (key, value) in key_value {
LEWrite::write(writer, key)?;
ser_amf3(writer, value)?;
}
LEWrite::write(writer, &Amf3String("".into()))?;
for value in &self.vec {
ser_amf3(writer, value)?;
}
Ok(())
}
}
/**
A type that can be (de-)serialized in the AMF3 format.
[See spec section 3 for more](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf-file-format-spec.pdf#%5B%7B%22num%22%3A20%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C88%2C305%2C0%5D).
*/
#[derive(Clone, GmParam, PartialEq)]
#[repr(u8)]
pub enum Amf3 {
False = 2,
True = 3,
Double(f64) = 5,
String(Amf3String) = 6,
Array(Amf3Array) = 9,
}
impl Debug for Amf3 {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::False => write!(f, "false"),
Self::True => write!(f, "true"),
Self::Double(x) => x.fmt(f),
Self::String(x) => x.fmt(f),
Self::Array (x) => x.fmt(f),
}
}
}
impl<R: Read> Deserialize<LE, R> for Amf3 {
fn deserialize(reader: &mut R) -> Res<Self> {
let mut reader = Amf3Reader { inner: reader, string_ref_table: vec![] };
deser_amf3(&mut reader)
}
}
fn deser_amf3<R: Read>(reader: &mut Amf3Reader<R>) -> Res<Amf3> {
let disc: u8 = LERead::read(reader)?;
Ok(match disc {
2 => Amf3::False,
3 => Amf3::True,
5 => Amf3::Double(LERead::read(reader)?),
6 => Amf3::String(LERead::read(reader)?),
9 => Amf3::Array (LERead::read(reader)?),
_ => { return Err(Error::new(InvalidData, format!("invalid discriminant value for Amf3: {}", disc))) }
})
}
impl<'a, W: Write> Serialize<LE, W> for &'a Amf3 {
fn serialize(self, writer: &mut W) -> Res<()> {
let mut writer = Amf3Writer { inner: writer, string_ref_table: vec![] };
ser_amf3(&mut writer, self)
}
}
fn ser_amf3<W: Write>(writer: &mut Amf3Writer<W>, amf3: &Amf3) -> Res<()> {
match amf3 {
Amf3::False => LEWrite::write(writer, 2u8),
Amf3::True => LEWrite::write(writer, 3u8),
Amf3::Double(x) => { LEWrite::write(writer, 5u8)?; LEWrite::write(writer, x) },
Amf3::String(x) => { LEWrite::write(writer, 6u8)?; LEWrite::write(writer, x) },
Amf3::Array (x) => { LEWrite::write(writer, 9u8)?; LEWrite::write(writer, x) },
}
}
impl From<bool> for Amf3 {
fn from(b: bool) -> Self {
if b { Self::True } else { Self::False }
}
}
impl From<f32> for Amf3 {
fn from(f: f32) -> Self {
Self::Double(f.into())
}
}
impl From<f64> for Amf3 {
fn from(f: f64) -> Self {
Self::Double(f)
}
}
impl TryFrom<&str> for Amf3 {
type Error = U29Error;
fn try_from(string: &str) -> Result<Self, Self::Error> {
Ok(Self::String(string.try_into()?))
}
}
#[cfg(test)]
mod tests {
use endio::{LERead, LEWrite};
use super::U29;
#[test]
fn test_u29() {
for (bytes, integer) in &[(&b"\x7f"[..], 0x7f), (&b"\xa2\x43"[..], 4419), (&b"\x88\x00"[..], 1024), (&b"\xff\xff\x7e"[..], 0x1ffffe), (&b"\x80\xc0\x80\x00"[..], 0x200000), (&b"\xbf\xff\xff\xfe"[..], 0xffffffe)] {
let mut reader = &bytes[..];
let val: U29 = reader.read().unwrap();
assert_eq!(val.0, *integer);
let mut writer = vec![];
writer.write(&val).unwrap();
assert_eq!(&&writer[..], bytes);
}
}
}
+9 -2
View File
@@ -5,8 +5,8 @@ use lu_packets_derive::{GameMessage, GmParam, VariantTests};
use crate::common::{ObjId, OBJID_EMPTY};
use crate::world::{CloneId, CLONE_ID_INVALID, Lot, LOT_NULL, MapId, MAP_ID_INVALID, Quaternion, Vector3, ZoneId};
use crate::world::lnv::LuNameValue;
use crate::world::{CloneId, CLONE_ID_INVALID, Lot, LOT_NULL, LuNameValue, MapId, MAP_ID_INVALID, Quaternion, Vector3, ZoneId};
use crate::world::amf3::Amf3;
use super::{EquipInventory, GmString, GmWString, InventoryType, KillType, UnEquipInventory, MissionState, PetNotificationType, MoveItemInInventory, MoveInventoryBatch, RemoveSkill, SetIgnoreProjectileCollision};
#[derive(Debug, Deserialize, PartialEq, Serialize)]
@@ -128,6 +128,7 @@ pub enum GameMessage {
EchoSyncSkill(EchoSyncSkill) = 1144,
DoClientProjectileImpact(DoClientProjectileImpact) = 1151,
SetPlayerAllowedRespawn(SetPlayerAllowedRespawn) = 1165,
UiMessageServerToSingleClient(UiMessageServerToSingleClient) = 1184,
UncastSkill(UncastSkill) = 1206,
FireEventClientSide(FireEventClientSide) = 1213,
ChangeObjectWorldState(ChangeObjectWorldState) = 1223,
@@ -1247,6 +1248,12 @@ pub struct SetPlayerAllowedRespawn {
pub dont_prompt_for_respawn: bool,
}
#[derive(Debug, GameMessage, PartialEq)]
pub struct UiMessageServerToSingleClient {
pub args: Amf3,
pub message_name: GmString,
}
#[derive(Debug, GameMessage, PartialEq)]
pub struct UncastSkill {
pub skill_id: i32, // todo: type
@@ -0,0 +1,12 @@
GameMessage::UiMessageServerToSingleClient(
UiMessageServerToSingleClient {
args: amf3! {
"false": false,
"true": true,
"double": 3.14,
"string": "string",
"array": amf3! ["inner", "array", true],
},
message_name: lu!(&b"QueueChoiceBox"[..]),
},
)
+1 -1
View File
@@ -11,7 +11,7 @@ use super::gm::GmParam;
use crate::common::{LuStrExt, LuVarString, LuVarWString, LuWStr};
/// A value contained in a [`LuNameValue`]
/// A value contained in a [`LuNameValue`].
#[derive(Deserialize, PartialEq, Serialize)]
#[repr(u8)]
pub enum LnvValue {
+1
View File
@@ -1,6 +1,7 @@
//! World messages.
pub mod client;
pub mod gm;
pub mod amf3;
mod lnv;
pub mod server;