mirror of
https://github.com/eigenein/wotbreplay-parser.git
synced 2026-10-21 10:27:01 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d601082d6 | ||
|
|
632f1781e7 | ||
|
|
fa6ac70d21 | ||
|
|
1fb4bea267 | ||
|
|
705280fc19 | ||
|
|
3f557e33bf |
No files matched your search
@@ -43,17 +43,11 @@ jobs:
|
|||||||
command: test
|
command: test
|
||||||
args: --all-targets
|
args: --all-targets
|
||||||
|
|
||||||
- name: 🧪 Test all features
|
- name: 🧪 Test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --all-targets --all-features
|
args: --all-features
|
||||||
|
|
||||||
- name: 🧪 Test docs
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --doc --all-features
|
|
||||||
|
|
||||||
- name: 🪄 Clippy
|
- name: 🪄 Clippy
|
||||||
uses: actions-rs/clippy-check@v1
|
uses: actions-rs/clippy-check@v1
|
||||||
|
|||||||
@@ -14,23 +14,24 @@ World of Tanks Blitz replay parser in Rust.
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use wotbreplay_parser::prelude::*;
|
use wotbreplay_parser::models::battle_results::TeamNumber;
|
||||||
|
use wotbreplay_parser::replay::Replay;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let battle_results = Replay::open(File::open("tests/replays/battle_results.wotbreplay")?)?
|
let battle_results = Replay::open(File::open("tests/replays/player_results.wotbreplay")?)?
|
||||||
.read_battle_results()?;
|
.read_battle_results()?;
|
||||||
|
|
||||||
assert_eq!(battle_results.timestamp, 1670018359);
|
assert_eq!(battle_results.timestamp, 1670083956);
|
||||||
assert_eq!(battle_results.players.len(), 14);
|
assert_eq!(battle_results.players.len(), 14);
|
||||||
|
|
||||||
assert_eq!(battle_results.players[0].account_id, 520886428);
|
assert_eq!(battle_results.players[0].account_id, 595693744);
|
||||||
assert_eq!(battle_results.players[0].info.nickname, "77mmmr");
|
assert_eq!(battle_results.players[0].info.nickname, "yuranhik_hustriy26");
|
||||||
assert_eq!(battle_results.players[0].info.team(), TeamNumber::Two);
|
assert_eq!(battle_results.players[0].info.team(), TeamNumber::One);
|
||||||
assert_eq!(battle_results.players[0].info.platoon_id, None);
|
assert_eq!(battle_results.players[0].info.platoon_id, Some(545104609));
|
||||||
|
|
||||||
assert_eq!(battle_results.players[1].info.nickname, "SNAK_THE_RIPPER");
|
assert_eq!(battle_results.players[1].info.nickname, "SNAK_THE_RIPPER");
|
||||||
assert_eq!(battle_results.players[1].info.team(), TeamNumber::One);
|
assert_eq!(battle_results.players[1].info.team(), TeamNumber::Two);
|
||||||
assert_eq!(battle_results.players[1].info.platoon_id, Some(547466834));
|
assert_eq!(battle_results.players[1].info.platoon_id, Some(273692628));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -6,10 +6,10 @@ pub enum Error {
|
|||||||
ZipError(#[from] zip::result::ZipError),
|
ZipError(#[from] zip::result::ZipError),
|
||||||
|
|
||||||
#[error("Protocol Buffers error")]
|
#[error("Protocol Buffers error")]
|
||||||
DecodeFailed(#[from] prost::DecodeError),
|
ProtobufError(#[from] prost::DecodeError),
|
||||||
|
|
||||||
#[error("Pickle error")]
|
#[error("Pickle error")]
|
||||||
UnpickleFailed(#[from] serde_pickle::Error),
|
PickleError(#[from] serde_pickle::Error),
|
||||||
|
|
||||||
#[error("I/O error")]
|
#[error("I/O error")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
@@ -19,7 +19,7 @@ pub enum Error {
|
|||||||
|
|
||||||
#[cfg(feature = "meta")]
|
#[cfg(feature = "meta")]
|
||||||
#[error("JSON error")]
|
#[error("JSON error")]
|
||||||
JsonDecodeFailed(#[from] serde_json::Error),
|
JsonError(#[from] serde_json::Error),
|
||||||
|
|
||||||
#[error("invalid magic: {0:#X}, expected: {1:#X}")]
|
#[error("invalid magic: {0:#X}, expected: {1:#X}")]
|
||||||
InvalidMagic(u32, u32),
|
InvalidMagic(u32, u32),
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ impl Data {
|
|||||||
let Some(packet) = Packet::from_reader(&mut reader)? else { break };
|
let Some(packet) = Packet::from_reader(&mut reader)? else { break };
|
||||||
match packet.payload {
|
match packet.payload {
|
||||||
Payload::ReplayHeader(header) => {
|
Payload::ReplayHeader(header) => {
|
||||||
this.replay_header = Some(header);
|
this.replay_header = Some(*header);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
this.other_packets.push(packet);
|
this.other_packets.push(packet);
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use crate::result::Result;
|
use crate::result::Result;
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub enum EntityMethod {
|
pub enum EntityMethod {
|
||||||
/// TODO: team composition.
|
/// TODO: team composition.
|
||||||
Subtype2F,
|
Subtype2F(#[serde_as(as = "serde_with::hex::Hex")] Vec<u8>),
|
||||||
|
|
||||||
/// Default variant when subtype is not known.
|
/// Default variant when subtype is not known.
|
||||||
Unknown { sub_type: u32, payload: Vec<u8> },
|
Unknown {
|
||||||
|
sub_type: u32,
|
||||||
|
|
||||||
|
/// Whole packet payload (including the sub-type).
|
||||||
|
#[serde_as(as = "serde_with::hex::Hex")]
|
||||||
|
payload: Vec<u8>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityMethod {
|
impl EntityMethod {
|
||||||
@@ -20,7 +28,7 @@ impl EntityMethod {
|
|||||||
let sub_type = reader.read_u32::<LittleEndian>()?;
|
let sub_type = reader.read_u32::<LittleEndian>()?;
|
||||||
|
|
||||||
let this = match sub_type {
|
let this = match sub_type {
|
||||||
0x2F => Self::Subtype2F,
|
0x2F => Self::Subtype2F(payload),
|
||||||
_ => Self::Unknown { sub_type, payload },
|
_ => Self::Unknown { sub_type, payload },
|
||||||
};
|
};
|
||||||
Ok(this)
|
Ok(this)
|
||||||
|
|||||||
@@ -11,11 +11,17 @@ use crate::result::Result;
|
|||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub enum Payload {
|
pub enum Payload {
|
||||||
ReplayHeader(ReplayHeader),
|
ReplayHeader(Box<ReplayHeader>),
|
||||||
EntityMethod(EntityMethod),
|
EntityMethod(EntityMethod),
|
||||||
|
|
||||||
/// Default payload when type is not known.
|
/// Default payload when type is not known.
|
||||||
Unknown(#[serde_as(as = "serde_with::hex::Hex")] Vec<u8>),
|
Unknown {
|
||||||
|
type_: u32,
|
||||||
|
|
||||||
|
/// Whole packet payload.
|
||||||
|
#[serde_as(as = "serde_with::hex::Hex")]
|
||||||
|
raw: Vec<u8>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
@@ -34,17 +40,20 @@ impl Payload {
|
|||||||
assert_magic(reader.read_u8()?, 0x00)?;
|
assert_magic(reader.read_u8()?, 0x00)?;
|
||||||
read_pickled(&mut reader, pickled_length as usize)?
|
read_pickled(&mut reader, pickled_length as usize)?
|
||||||
};
|
};
|
||||||
Self::ReplayHeader(ReplayHeader {
|
Self::ReplayHeader(Box::new(ReplayHeader {
|
||||||
summary,
|
summary,
|
||||||
author_nickname,
|
author_nickname,
|
||||||
arena_unique_id,
|
arena_unique_id,
|
||||||
arena_type_id,
|
arena_type_id,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
8 => Self::EntityMethod(EntityMethod::new(payload)?),
|
8 => Self::EntityMethod(EntityMethod::new(payload)?),
|
||||||
|
|
||||||
_ => Self::Unknown(payload),
|
_ => Self::Unknown {
|
||||||
|
raw: payload,
|
||||||
|
type_,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,69 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Summary {
|
pub struct Summary {
|
||||||
#[serde(rename = "playersBattleCategoriesIds")]
|
#[serde(rename(deserialize = "playersBattleCategoriesIds"))]
|
||||||
pub players_battle_categories_ids: HashMap<u32, (u8, u32)>,
|
pub players_battle_categories_ids: HashMap<u32, (u8, u32)>,
|
||||||
|
|
||||||
#[serde(rename = "battleLevel")]
|
#[serde(rename(deserialize = "battleLevel"))]
|
||||||
pub battle_level: u8,
|
pub battle_level: u8,
|
||||||
|
|
||||||
#[serde(rename = "battleCategoryId")]
|
#[serde(rename(deserialize = "battleCategoryId"))]
|
||||||
pub battle_category_id: u8,
|
pub battle_category_id: u8,
|
||||||
|
|
||||||
#[serde(rename = "mouseEnabled")]
|
#[serde(rename(deserialize = "mouseEnabled"))]
|
||||||
pub is_mouse_enabled: bool,
|
pub is_mouse_enabled: bool,
|
||||||
|
|
||||||
#[serde(rename = "mmType")]
|
#[serde(rename(deserialize = "mmType"))]
|
||||||
pub matchmaker_type: u8,
|
pub matchmaker_type: u8,
|
||||||
|
|
||||||
#[serde(rename = "camouflageSlot")]
|
#[serde(rename(deserialize = "camouflageSlot"))]
|
||||||
pub camouflage_slot: u8,
|
pub camouflage_slot: u8,
|
||||||
|
|
||||||
#[serde(rename = "avgMmr")]
|
#[serde(rename(deserialize = "avgMmr"))]
|
||||||
pub average_mmr: Vec<f64>,
|
pub average_mmr: Vec<Option<f64>>,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "playerWaitTimes"))]
|
||||||
|
pub player_wait_times: HashMap<u32, f64>,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "accountDatabaseIds"))]
|
||||||
|
pub account_database_ids: Vec<u32>,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "turboBattlesStats"))]
|
||||||
|
pub turbo_battles_statistics: TurboBattlesStatistics,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TurboBattlesStatistics {
|
||||||
|
#[serde(rename(deserialize = "turboBattleChance"))]
|
||||||
|
pub battle_chance: f64,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "turboLeaveChance"))]
|
||||||
|
pub leave_chance: f64,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "possibleTurboBattlesNotForSkip"))]
|
||||||
|
pub possible_battles_not_for_skip: u32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "possibleTurboBattlesForSkip"))]
|
||||||
|
pub possible_battles_for_skip: u32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "turboBattlesForSkip"))]
|
||||||
|
pub battles_for_skip: u32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "turboBattlesNotForSkip"))]
|
||||||
|
pub battles_not_for_skip: u32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "abSkip"))]
|
||||||
|
pub ab_skip: i32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "beta"))]
|
||||||
|
pub beta: f64,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "premiumVehDiff"))]
|
||||||
|
pub premium_vehicle_difference: i32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "htDiff"))]
|
||||||
|
pub ht_difference: i32,
|
||||||
|
|
||||||
|
#[serde(rename(deserialize = "alpha"))]
|
||||||
|
pub alpha: f64,
|
||||||
}
|
}
|
||||||
+6
-14
@@ -12,7 +12,13 @@ fn player_results_ok() -> Result<()> {
|
|||||||
let battle_results: BattleResults = battle_results_dat.try_into()?;
|
let battle_results: BattleResults = battle_results_dat.try_into()?;
|
||||||
|
|
||||||
assert_eq!(battle_results.winner_team_number, 2);
|
assert_eq!(battle_results.winner_team_number, 2);
|
||||||
|
|
||||||
assert_eq!(battle_results.author.team_number, 2);
|
assert_eq!(battle_results.author.team_number, 2);
|
||||||
|
assert_eq!(battle_results.author.credits_earned, 59518);
|
||||||
|
assert_eq!(battle_results.author.combat_xp, 5255);
|
||||||
|
assert_eq!(battle_results.author.damage_dealt, 1438);
|
||||||
|
assert_eq!(battle_results.author.account_id, 594778041);
|
||||||
|
assert_eq!(battle_results.free_xp, 260);
|
||||||
|
|
||||||
let snak_the_ripper_info = &battle_results.player_results[3].info;
|
let snak_the_ripper_info = &battle_results.player_results[3].info;
|
||||||
assert_eq!(snak_the_ripper_info.base_xp, 929);
|
assert_eq!(snak_the_ripper_info.base_xp, 929);
|
||||||
@@ -111,17 +117,3 @@ fn victory_points_ok() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn author_results_ok() -> Result<()> {
|
|
||||||
let battle_results = Replay::open(File::open("tests/replays/author_results.wotbreplay")?)?
|
|
||||||
.read_battle_results()?;
|
|
||||||
|
|
||||||
assert_eq!(battle_results.author.credits_earned, 53623);
|
|
||||||
assert_eq!(battle_results.author.combat_xp, 2370);
|
|
||||||
assert_eq!(battle_results.author.damage_dealt, 1738);
|
|
||||||
assert_eq!(battle_results.author.account_id, 594778041);
|
|
||||||
assert_eq!(battle_results.free_xp, 116);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
+17
-8
@@ -5,22 +5,31 @@ use wotbreplay_parser::replay::Replay;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn data_ok() -> Result<()> {
|
fn data_ok() -> Result<()> {
|
||||||
let mut replay = Replay::open(File::open("tests/replays/player_results.wotbreplay")?)?;
|
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 data = replay.read_data()?;
|
||||||
|
|
||||||
assert_eq!(data.client_version, "9.4.0_apple");
|
assert_eq!(data.client_version, "9.8.5_apple");
|
||||||
assert_eq!(data.other_packets.len(), 44720);
|
assert_eq!(data.other_packets.len(), 60138);
|
||||||
|
|
||||||
let header = data.replay_header.unwrap();
|
let header = data.replay_header.unwrap();
|
||||||
assert_eq!(header.author_nickname, "zeekrab");
|
assert_eq!(header.author_nickname, "helaas_pindakaas");
|
||||||
assert_eq!(header.arena_unique_id, 1661909200500084);
|
assert_eq!(header.arena_unique_id, 16114615898101270);
|
||||||
assert_eq!(header.arena_type_id, 65567);
|
assert_eq!(header.arena_type_id, 43);
|
||||||
assert_eq!(header.summary.battle_level, 8);
|
assert_eq!(header.summary.battle_level, 9);
|
||||||
assert_eq!(header.summary.battle_category_id, 13);
|
assert_eq!(header.summary.battle_category_id, 13);
|
||||||
assert!(header.summary.is_mouse_enabled);
|
assert!(header.summary.is_mouse_enabled);
|
||||||
assert_eq!(header.summary.matchmaker_type, 2);
|
assert_eq!(header.summary.matchmaker_type, 2);
|
||||||
assert_eq!(header.summary.camouflage_slot, 1);
|
assert_eq!(header.summary.camouflage_slot, 1);
|
||||||
assert_eq!(header.summary.average_mmr, [-25.396859946846963, 77.58806455880404]);
|
assert_eq!(header.summary.average_mmr, [Some(59.29457753400008), Some(99.34182121604681)]);
|
||||||
|
assert_eq!(
|
||||||
|
header.summary.account_database_ids,
|
||||||
|
[
|
||||||
|
517033644, 537867068, 597583401, 601893865, 576810156, 531053956, 599580824, 558025185,
|
||||||
|
526927603, 594778041, 505587490, 597422303, 531923111, 587617355
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Reference in new issue
Block a user