From 72165ae52c17369e95b4c4a63f5c9f8538b17d62 Mon Sep 17 00:00:00 2001 From: Pavel Perestoronin Date: Sun, 30 Apr 2023 22:13:51 +0200 Subject: [PATCH] Break: decode the players packet --- src/models/data.rs | 22 ++++++++++++++--- src/models/data/entity_method.rs | 25 ++++++++++++++----- src/models/data/payload.rs | 8 +++--- src/models/data/subtype_2f.rs | 33 ++++++++++++++++++++++++- tests/data.rs | 42 +++++++++++++++++++++++++++++++- 5 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/models/data.rs b/src/models/data.rs index 8020674..3a9bf8a 100644 --- a/src/models/data.rs +++ b/src/models/data.rs @@ -33,7 +33,7 @@ impl Data { reader.read_u64::()?; // Some sort of client hash, e.g.: `6CF2A9EFA5C52D6F6CE43A6D4A699C05`: - read_bytes(&mut reader)?; + read_length_delimited(&mut reader)?; let client_version = read_string(&mut reader)?; @@ -59,7 +59,7 @@ fn assert_magic + PartialEq>(actual: T, expected: T) -> Result { } #[inline] -fn read_bytes(reader: &mut impl Read) -> Result> { +fn read_length_delimited(reader: &mut impl Read) -> Result> { let length = reader.read_u8()? as usize; let mut buffer = Vec::new(); buffer.resize(length, 0); @@ -69,7 +69,7 @@ fn read_bytes(reader: &mut impl Read) -> Result> { #[inline] fn read_string(reader: &mut impl Read) -> Result { - Ok(String::from_utf8(read_bytes(reader)?)?) + Ok(String::from_utf8(read_length_delimited(reader)?)?) } #[inline] @@ -79,3 +79,19 @@ fn read_pickled(reader: &mut impl Read, length: usize) -> R reader.read_exact(&mut buffer)?; Ok(serde_pickle::from_slice(&buffer, Default::default())?) } + +/// Read 2-byte length surrounded by the magical `0xFF` and `0x00`, +/// unless it's a simple 1-byte length. +/// +/// P.S. Okay, Wargaming, I've no idea what you smoke, but I want some! +#[inline] +fn read_quirky_length(reader: &mut impl Read) -> Result { + match reader.read_u8()? { + 0xFF => { + let length = reader.read_u16::()?; + assert_magic(reader.read_u8()?, 0x00)?; + Ok(length as usize) + } + length => Ok(length as usize), + } +} diff --git a/src/models/data/entity_method.rs b/src/models/data/entity_method.rs index 0fbf70f..6eeebae 100644 --- a/src/models/data/entity_method.rs +++ b/src/models/data/entity_method.rs @@ -1,16 +1,23 @@ -use std::io::Read; - use byteorder::{LittleEndian, ReadBytesExt}; +use prost::encoding::decode_varint; +use prost::Message; use serde::Serialize; use serde_with::serde_as; +use crate::models::data::read_quirky_length; +use crate::models::data::subtype_2f::Subtype2F; use crate::result::Result; #[serde_as] #[derive(Debug, Serialize)] pub enum EntityMethod { Subtype2F { - // prefix: [u8; 9], + field_number: u64, + sub_type_2f: Subtype2F, + + /// Original payload for investigation. + #[serde_as(as = "serde_with::hex::Hex")] + payload: Vec, }, /// Default variant when subtype is not known by the parser. @@ -32,9 +39,15 @@ impl EntityMethod { let this = match sub_type { 0x2F => { - // let mut prefix = [0; 9]; - // reader.read_exact(&mut prefix)?; - Self::Subtype2F {} + let _inner_length = reader.read_u32::()?; + + let field_number = decode_varint(&mut reader)?; + let message_length = read_quirky_length(&mut reader)?; + Self::Subtype2F { + field_number, + sub_type_2f: Subtype2F::decode(&reader[..message_length])?, + payload, + } } _ => Self::Unknown { sub_type, payload }, diff --git a/src/models/data/payload.rs b/src/models/data/payload.rs index cfe568c..4939e23 100644 --- a/src/models/data/payload.rs +++ b/src/models/data/payload.rs @@ -6,7 +6,7 @@ use serde_with::serde_as; use crate::models::data::entity_method::EntityMethod; use crate::models::data::type_0::Type0; -use crate::models::data::{assert_magic, read_pickled, read_string}; +use crate::models::data::{read_pickled, read_quirky_length, read_string}; use crate::result::Result; #[serde_as] @@ -43,10 +43,8 @@ impl Payload { let arena_unique_id = reader.read_u64::()?; let arena_type_id = reader.read_u32::()?; let type_0 = { - assert_magic(reader.read_u8()?, 0xFF)?; - let pickled_length = reader.read_u16::()?; - assert_magic(reader.read_u8()?, 0x00)?; - read_pickled(&mut reader, pickled_length as usize)? + let pickled_length = read_quirky_length(&mut reader)?; + read_pickled(&mut reader, pickled_length)? }; Self::Type0 { type_0, diff --git a/src/models/data/subtype_2f.rs b/src/models/data/subtype_2f.rs index 11f0a73..390d309 100644 --- a/src/models/data/subtype_2f.rs +++ b/src/models/data/subtype_2f.rs @@ -1 +1,32 @@ -pub struct Subtype2f {} +use prost::Message; +use serde::Serialize; + +#[derive(Message, Serialize)] +pub struct Subtype2F { + #[prost(message, tag = "1")] + pub players: Option, +} + +#[derive(Message, Serialize)] +pub struct Players { + #[prost(message, repeated, tag = "1")] + pub players: Vec, +} + +#[derive(Message, Serialize)] +pub struct Player { + #[prost(string, required, tag = "3")] + pub nickname: String, + + #[prost(uint32, required, tag = "4")] + pub team_number: u32, + + #[prost(uint32, required, tag = "7")] + pub account_id: u32, + + #[prost(string, optional, tag = "8")] + pub clan_tag: Option, + + #[prost(uint32, optional, tag = "11")] + pub platoon_number: Option, +} diff --git a/tests/data.rs b/tests/data.rs index 93a7fca..3cce922 100644 --- a/tests/data.rs +++ b/tests/data.rs @@ -1,11 +1,12 @@ use std::fs::File; use anyhow::Result; +use wotbreplay_parser::models::data::entity_method::EntityMethod; use wotbreplay_parser::models::data::payload::Payload; use wotbreplay_parser::replay::Replay; #[test] -fn data_ok() -> Result<()> { +fn parse_ok() -> Result<()> { let mut replay = Replay::open(File::open( "tests/replays/20230429_0126__helaas_pindakaas_A140_ASTRON_REX_105_16114615898101270.wotbreplay", )?)?; @@ -14,6 +15,16 @@ fn data_ok() -> Result<()> { assert_eq!(data.client_version, "9.8.5_apple"); assert_eq!(data.packets.len(), 60139); + Ok(()) +} + +#[test] +fn type0_ok() -> Result<()> { + let mut replay = Replay::open(File::open( + "tests/replays/20230429_0126__helaas_pindakaas_A140_ASTRON_REX_105_16114615898101270.wotbreplay", + )?)?; + let data = replay.read_data()?; + let (author_nickname, arena_unique_id, arena_type_id, type_0) = data .packets .iter() @@ -51,3 +62,32 @@ fn data_ok() -> Result<()> { Ok(()) } + +#[test] +fn subtype_2f_players_ok() -> Result<()> { + let mut replay = Replay::open(File::open( + "tests/replays/20230429_0126__helaas_pindakaas_A140_ASTRON_REX_105_16114615898101270.wotbreplay", + )?)?; + let data = replay.read_data()?; + + let players = data + .packets + .iter() + .filter_map(|packet| match &packet.payload { + Payload::EntityMethod(EntityMethod::Subtype2F { sub_type_2f, .. }) => { + sub_type_2f.players.as_ref() + } + _ => None, + }) + .next() + .unwrap(); + + assert_eq!(players.players.len(), 14); + assert_eq!(players.players[1].team_number, 1); + assert_eq!(players.players[1].nickname, "Igrok_WoT_blitz"); + assert_eq!(players.players[1].account_id, 597583401); + assert_eq!(players.players[1].platoon_number, None); + assert_eq!(players.players[1].clan_tag, Some("F_ANG".to_string())); + + Ok(()) +}