From e037ed01d3e5a224fdfe80ab6ef7c65d864e36c5 Mon Sep 17 00:00:00 2001 From: Lane Kolbly Date: Sun, 23 Aug 2020 12:24:27 -0500 Subject: [PATCH] QoL features for replaying to aid decoding. Update readme. --- README.md | 31 +++++++++++-- parser/src/packet.rs | 6 +-- replayshark/Cargo.toml | 3 -- replayshark/src/main.rs | 100 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ed84878..c70172f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ which indicates that the recipient `604965` received 1254 units of damage from ` The `wowsreplay` format has a majority of packets under the `7` and `8` packet types, which I refer to as "Entity" packets. These packets have not yet been decoded (their packet ID is unknown), although it is known that they contain an entity ID (that's all that's known about them). -Some packets will appear as "Invalid" packets, these are packets for which the packet ID is known, but for some reason the parser decided it didn't know what to do with the packet. +Some packets will appear as "Invalid" packets, these are packets for which the packet ID is known, but for some reason the parser decided it didn't know what to do with the packet. If you find one of these, please feel free to copy the packet into a new issue! Trace Utility ============= @@ -56,9 +56,34 @@ The following game versions are currently supported: - 0.9.6 (and .1) - 0.9.7 -The version policy for this component is forward-looking: After game version X is released, I won't work very hard to decode new packets from version X-1 and below. To the extent practical, though, support for older versions will be maintained. +The version policy for this component is forward-looking: After game version X is released, I won't work very hard to decode new packets from version X-1 and below. To the extent practical, though, support for older versions will be maintained - but it is not guaranteed that any version other than the "current" will work. + +Packet Support +============== + +The following packets are parsed at least partially: +- The initial "Setup" packet which enumerates the players, their ships, camo configuration, etc. is partially decoded. + - The general structure is partially decoded. Most fields are not decoded. +- Position and PlayerOrientation: The position of other ships and of the player and of the camera. + - Some fields are not fully decoded. +- Chat: Emitted whenever a player sends a chat message. +- DamageReceived: Emitted whenever damage is dealt to a ship. +- ArtilleryHit: Emitted whenever either the player's guns hit a ship or whenever the player's ship is hit by shells. This packet has many unknown fields. +- Banner: Emitted when the player receives a banner. + +Packet wishlist +=============== + +The following information is not yet extracted from replays, but is information that is known to exist in the replay file: +- Torpedoes. +- Planes. +- Ship visibility/hidden status (and the player's "detected" status) +- Ship destruction. +- Incapacitation type. +- Consumable usage. +- Smoke. Contributing ============ -Feel free to open issues or PRs if you find any bugs or want to be able to parse any particular packets from your replay files. This project is in Rust, but the `dump` command can generate JSON data for consumption in any language, so if you end up writing a packet parser for a new packet in another language please open an issue and I can add it to the Rust code. +Feel free to open issues or PRs if you find any bugs or want to be able to parse any particular packets from your replay files. This project is in Rust, but the `dump` command can generate JSON data for consumption in any language, so if you end up writing a packet parser for a new packet in another language please open an issue and I can port it to the Rust code. diff --git a/parser/src/packet.rs b/parser/src/packet.rs index b294909..dea323c 100644 --- a/parser/src/packet.rs +++ b/parser/src/packet.rs @@ -80,7 +80,7 @@ pub struct PlayerOrientationPacket { /// Radians, 0 is North and positive numbers are clockwise /// e.g. pi/2 is due East, -pi/2 is due West, and +/-pi is due South. - pub bearing: f32, + pub heading: f32, pub f4: f32, pub f5: f32, @@ -176,7 +176,7 @@ fn parse_player_orientation_packet(i: &[u8]) -> IResult<&[u8], PacketType> { let (i, x) = le_f32(i)?; let (i, y) = le_f32(i)?; let (i, z) = le_f32(i)?; - let (i, bearing) = le_f32(i)?; + let (i, heading) = le_f32(i)?; let (i, f4) = le_f32(i)?; let (i, f5) = le_f32(i)?; Ok(( @@ -187,7 +187,7 @@ fn parse_player_orientation_packet(i: &[u8]) -> IResult<&[u8], PacketType> { x, y, z, - bearing, + heading, f4, f5, }), diff --git a/replayshark/Cargo.toml b/replayshark/Cargo.toml index dfd91ef..029ec90 100644 --- a/replayshark/Cargo.toml +++ b/replayshark/Cargo.toml @@ -11,14 +11,11 @@ wows-replays = { version = "0.1.0", path = "../parser" } nom = "6.0.0-alpha1" hexdump = "0.1.0" flate2 = "1.0.14" -rust-lzma = "0.5" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" thiserror = "1.0.19" -lzw = "0.10.0" rust-crypto = "0.2.36" -libz-sys = "1.0.25" plotters = "0.2.15" image = "0.23.4" clap = "2.33.1" diff --git a/replayshark/src/main.rs b/replayshark/src/main.rs index 46c94bb..d5e4fbf 100644 --- a/replayshark/src/main.rs +++ b/replayshark/src/main.rs @@ -341,11 +341,35 @@ fn main() { .help("Filter packets to be the given entity subtype") .takes_value(true), ) + .arg( + Arg::with_name("exclude-subtypes") + .long("exclude-subtypes") + .help("A comma-delimited list of Entity subtypes to exclude") + .takes_value(true) + ) + .arg( + Arg::with_name("timestamps") + .long("timestamps") + .help("A comma-delimited list of timestamps to highlight in the output") + .takes_value(true) + ) + .arg( + Arg::with_name("timestamp-offset") + .long("timestamp-offset") + .help("Number of seconds to subtract from the timestamps") + .takes_value(true) + ) .arg( Arg::with_name("no-meta") .long("no-meta") .help("Don't output the metadata"), ) + .arg( + Arg::with_name("speed") + .long("speed") + .help("Play back the file at the given speed multiplier") + .takes_value(true), + ) .arg(replay_arg.clone()), ) .get_matches(); @@ -360,12 +384,58 @@ fn main() { }, &std::path::PathBuf::from(input), |_, meta, packets| { + + let timestamp_offset: u32 = matches.value_of("timestamp-offset").unwrap_or("0").parse().expect("Couldn't parse timestamp-offset as float"); + + let mut timestamps: Vec = matches.value_of("timestamps").unwrap_or("").split(',').map(|x| { + if x.len() == 0 { // TODO: This is a workaround for empty + return 0; + } + let parts: Vec<&str> = x.split(':').collect(); + assert!(parts.len() == 2); + let m: u32 = parts[0].parse().unwrap(); + let s: u32 = parts[1].parse().unwrap(); + (m * 60 + s) - timestamp_offset + }).collect(); + timestamps.sort(); + if timestamps.len() == 1 && timestamps[0] == 0 { // TODO: Workaround for empty timestamps specifier + timestamps = vec!(); + } + //println!("{:?}", timestamps); + + #[derive(PartialEq)] + enum PacketIdent { + PacketType(u32), + WithSubtype((u32, u32)), + }; + + let mut exclude_packets: Vec = matches.value_of("exclude-subtypes").unwrap_or("").split(',').map(|x| { + if x.len() == 0 { // TODO: This is a workaround for empty lists + return PacketIdent::PacketType(0); + } + if x.contains(":") { + let parts: Vec<&str> = x.split(':').collect(); + assert!(parts.len() == 2); + let supertype = parts[0].parse().expect("Couldn't parse supertype"); + let subtype = parts[1].parse().expect("Couldn't parse subtype"); + PacketIdent::WithSubtype((supertype, subtype)) + } else { + PacketIdent::PacketType(x.parse().expect("Couldn't parse u32")) + } + //x.parse().expect("Couldn't parse exclude packet") + }).collect(); + if exclude_packets.len() == 0 && exclude_packets[0] == PacketIdent::PacketType(0) { // TODO: This is a workaround for empty lists + exclude_packets = vec!(); + } + if !matches.is_present("no-meta") { println!( "{}", serde_json::to_string(&meta).expect("Couldn't JSON-format metadata") ); } + let speed: u32 = matches.value_of("speed").map(|x| x.parse().expect("Couldn't parse speed! Must specify an integer")).unwrap_or(0); + let start_tm = std::time::Instant::now(); for packet in packets { let superfilter: Option = matches.value_of("filter-super").map(|x| x.parse().unwrap()); @@ -391,8 +461,38 @@ fn main() { } }, }; + /*if exclude_packets.len() > 0 { + let packet_type = if packet.packet_type == 7 || packet.packet_type == 8 { + match &packet.payload { + PacketType::Entity(p) => { + PacketIdent::WithSubtype((p.supertype, p.subtype)) + } + _ => { + // Skip known packets + continue; + } + } + } else { + PacketIdent::PacketType(packet.packet_type) + }; + if exclude_packets.contains(&packet_type) { + continue; + } + }*/ let s = serde_json::to_string(&packet) .expect("Couldn't JSON-format serialize packet"); + if speed > 0 { + let current_tm = start_tm.elapsed().as_secs_f32() * speed as f32; + if packet.clock > current_tm { + let millis = (packet.clock - current_tm) * 1000.0; + //println!("Sleeping for {}", millis); + std::thread::sleep(std::time::Duration::from_millis(millis as u64)); + } + } + if timestamps.len() > 0 && timestamps[0] < packet.clock as u32 { + println!("{{\"clock\":{},\"timestamp\":1}}", packet.clock); + timestamps.remove(0); + } println!("{}", s); } },