mirror of
https://github.com/QRouland/SGTool.git
synced 2024-09-08 00:25:21 +00:00
Initial parser for stormfate replay
This commit is contained in:
parent
1039ccf224
commit
d88e5410a0
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,3 +14,6 @@ Cargo.lock
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Project specific
|
||||
replays
|
||||
|
||||
|
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "sgtool"
|
||||
version = "0.0.1-dev"
|
||||
edition = "2021"
|
||||
authors = [ "Quentin Rouland <quentin@qrouland.com>" ]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
byteorder = "1.5"
|
||||
flate2 = "1.0"
|
||||
quick-protobuf = { version = "0.8", features = ["std"] }
|
||||
log = "0.4.21"
|
||||
pretty_env_logger = "0.5.0"
|
14
README.md
14
README.md
@ -1,2 +1,14 @@
|
||||
# SGTools
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
|
||||
|
||||
# SGTool
|
||||
|
||||
SGTool is a library and tool for analyze of the replay of the Stormgate game.
|
||||
|
||||
|
||||
# Acknowledgement
|
||||
|
||||
[shroudstone](https://github.com/acarapetis/shroudstone) : A project with similar goal wrote in python. We thank them for part of the reverse engineering notabally the protobuf schema also used this project.
|
||||
|
||||
# Disclamer
|
||||
|
||||
SGTool is community build which is not affiliate in any way to the official Stormgate Game in anyway.
|
10
gen_proto.sh
Executable file
10
gen_proto.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! command -v pb-rs &> /dev/null
|
||||
then
|
||||
echo "Failed to find pb-rs command"
|
||||
echo "To install it run : cargo install pb-rs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pb-rs src/protos/stormgate.proto
|
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
pub mod parser;
|
||||
mod protos;
|
27
src/main.rs
Normal file
27
src/main.rs
Normal file
@ -0,0 +1,27 @@
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use clap::Parser;
|
||||
use crate::parser::Replay;
|
||||
|
||||
|
||||
mod protos;
|
||||
mod parser;
|
||||
|
||||
/// Simple replay Stormgate replay parser
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path of replay to parse
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
let args = Args::parse();
|
||||
info!("Input : {}", args.input.to_string_lossy());
|
||||
let mut bytes = Vec::new();
|
||||
debug!("{:#?}", Replay::load_file(&args.input, &mut bytes).unwrap());
|
||||
}
|
101
src/parser.rs
Normal file
101
src/parser.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::{fs::OpenOptions, io::{BufRead, BufReader, Read}, path::Path};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use flate2::bufread::GzDecoder;
|
||||
use quick_protobuf::BytesReader;
|
||||
|
||||
use crate::protos::stormgate::ReplayChunk;
|
||||
|
||||
/// Stormgate Replay Header.
|
||||
/// It consists of 16 bytes at the top of the replay
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
h1: u32, // 12 first bytes repsentation are unknown at the moment
|
||||
build_number: u32, // 4 next is he build number
|
||||
}
|
||||
|
||||
/// Stormgate Replay Payload.
|
||||
/// After the the 16 bytes header, the actual payload of the replay of Protobuf messages that are gzipped
|
||||
/// Each Protobuf messages represents events that appended in the game from the differetns clients
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Payload<'a> {
|
||||
chunks: Vec<ReplayChunk<'a>>,
|
||||
}
|
||||
|
||||
/// Stormgate replay
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Replay<'a> {
|
||||
header: Header, // header of the replay
|
||||
payload: Payload<'a>, // the content of a replay is set of messages
|
||||
}
|
||||
|
||||
fn load_part<'a, R: Read>(reader: &'a mut R) -> impl FnMut(usize) -> Result<Vec<u8>, std::io::Error> + 'a {
|
||||
move |size| {
|
||||
let mut buf = vec![0u8; size];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
///
|
||||
fn load<T: BufRead>(reader: &mut T) -> Result<Header, std::io::Error> {
|
||||
let mut load = load_part(reader);
|
||||
let h1 = LittleEndian::read_u32(&load(12)?);
|
||||
let build_number = LittleEndian::read_u32(&load(4)?);
|
||||
Ok(Header { h1, build_number })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Payload<'a> {
|
||||
fn load<T: BufRead>(buf_reader: &mut T, buf: &'a mut Vec<u8>) -> Result<Payload<'a>, Box<dyn std::error::Error>> {
|
||||
let mut d = GzDecoder::new(buf_reader);
|
||||
d.read_to_end(buf)?;
|
||||
let mut reader = BytesReader::from_bytes(&buf);
|
||||
|
||||
let mut data = Vec::new();
|
||||
|
||||
while !reader.is_eof() {
|
||||
let read_message = reader.read_message::<ReplayChunk>(buf)?;
|
||||
data.push(read_message);
|
||||
}
|
||||
|
||||
Ok(Payload { chunks: data })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Replay<'a> {
|
||||
fn load<T: BufRead>(buf_reader: &mut T, buf: &'a mut Vec<u8>) -> Result<Replay<'a>, Box<dyn std::error::Error>> {
|
||||
// Get Header
|
||||
let header = Header::load(buf_reader)?;
|
||||
|
||||
// Get Payload
|
||||
let data = Payload::load(buf_reader, buf)?;
|
||||
|
||||
Ok(Replay { header, payload: data })
|
||||
}
|
||||
|
||||
pub fn load_file(path: &Path, buf: &'a mut Vec<u8>) -> Result<Replay<'a>, Box<dyn std::error::Error>> {
|
||||
let file = OpenOptions::new().read(true).open(path).unwrap();
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
Replay::load(&mut buf_reader, buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn load_file() {
|
||||
// Replay used is form Alpha phase of stormgate, threfore is not provided in the repo to avoid any problem with NDA at this time
|
||||
// TODO : when the game officially can out fix provide sample replay for testing directly in the repo
|
||||
let replay_path = Path::new("replays/CL55366-2024.05.12-01.53.SGReplay");
|
||||
let mut buffer : Vec<u8> = vec![];
|
||||
let r = Replay::load_file(replay_path, &mut buffer).unwrap();
|
||||
assert_eq!(r.header.build_number, 55366);
|
||||
assert_eq!(r.payload.chunks.len(), 1373);
|
||||
}
|
||||
}
|
||||
|
1
src/protos/mod.rs
Normal file
1
src/protos/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod stormgate;
|
104
src/protos/stormgate.proto
Normal file
104
src/protos/stormgate.proto
Normal file
@ -0,0 +1,104 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package stormgate;
|
||||
|
||||
// protobuf schema for parsing lobby info from Stormgate replays
|
||||
// Thanks to CascadeFury for most of this.
|
||||
|
||||
// Each chunk seems to be of the form 3: {1: {actual content}}
|
||||
// Since I don't know the meaning of those outer structs yet, I'm just putting them as inline messages:
|
||||
message ReplayChunk {
|
||||
int32 timestamp = 1; // Time since game start in units of 1/1024 seconds
|
||||
int32 client_id = 2;
|
||||
message Wrapper {
|
||||
message ReplayContent {
|
||||
oneof content_type {
|
||||
Map map = 3;
|
||||
Player player = 12;
|
||||
LobbyChangeSlot change_slot = 13;
|
||||
LobbySetVariable set_variable = 15;
|
||||
StartGame start_game = 18;
|
||||
PlayerLeftGame player_left_game = 25;
|
||||
AssignPlayerSlot assign_player_slot = 37;
|
||||
}
|
||||
}
|
||||
ReplayContent content = 1;
|
||||
}
|
||||
Wrapper inner = 3;
|
||||
}
|
||||
|
||||
// 3 - Map
|
||||
message Map {
|
||||
string folder = 1;
|
||||
string name = 2;
|
||||
int32 seed = 3;
|
||||
}
|
||||
|
||||
message Player {
|
||||
UUID uuid = 2;
|
||||
message PlayerName {
|
||||
string nickname = 1;
|
||||
string discriminator = 2;
|
||||
}
|
||||
PlayerName name = 3;
|
||||
}
|
||||
|
||||
// 13 - Sent when player changes slot (leave/enter), note that SlotChoice
|
||||
// contains either a request for a specific slot, or what I assume is a "next
|
||||
// slot available", slot 255 is observer
|
||||
message LobbyChangeSlot {
|
||||
message SlotChoice {
|
||||
message SpecificSlot {
|
||||
int32 slot = 1;
|
||||
}
|
||||
oneof choice_type {
|
||||
SpecificSlot specific_slot = 2;
|
||||
}
|
||||
}
|
||||
|
||||
SlotChoice choice = 1;
|
||||
}
|
||||
|
||||
// 15 - Sent when a player slot has a variable changed
|
||||
// Var 374945738: Type, 0 = Closed, 1 = Human, 2 = AI
|
||||
// Var 2952722564: Faction, 0 = Vanguard, 1 = Infernals
|
||||
// Var 655515685: AIType, 0 = Peaceful, 1 = Junior, 2 = Senior
|
||||
message LobbySetVariable {
|
||||
int32 slot = 3;
|
||||
uint32 variable_id = 4;
|
||||
uint32 value = 5;
|
||||
}
|
||||
|
||||
// 18 - "Start Game" Sent by players after second ReadyUp, that probably indicates player finished loading into the map
|
||||
message StartGame {
|
||||
}
|
||||
|
||||
|
||||
enum LeaveReason {
|
||||
Unknown = 0;
|
||||
Surrender = 1; // Player surrenders
|
||||
Leave = 2; // Player leaves game normally (game ended earlier, if this is the first PlayerLeftGame message, the outcome should be considered unknown)
|
||||
}
|
||||
|
||||
// 25 - When a player exits the game/disconnects
|
||||
message PlayerLeftGame {
|
||||
UUID player_uuid = 1;
|
||||
LeaveReason reason = 2;
|
||||
}
|
||||
|
||||
// 37 - AssignPlayer (done by server as pl=64)
|
||||
// Appears only in ladder games?
|
||||
message AssignPlayerSlot {
|
||||
UUID uuid = 1;
|
||||
int64 slot = 2;
|
||||
string nickname = 3;
|
||||
}
|
||||
|
||||
// UUIDs are encoded as 2 varints.
|
||||
// To recover the original UUID, encode these as signed 64-bit big-endian
|
||||
// integers and concatenate the resulting bitstrings; or in python:
|
||||
// uuid.UUID(bytes=struct.pack(">qq", part1, part2))
|
||||
message UUID {
|
||||
int64 part1 = 1;
|
||||
int64 part2 = 2;
|
||||
}
|
619
src/protos/stormgate.rs
Normal file
619
src/protos/stormgate.rs
Normal file
@ -0,0 +1,619 @@
|
||||
// Automatically generated rust module for 'stormgate.proto' file
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
|
||||
use std::borrow::Cow;
|
||||
use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
|
||||
use quick_protobuf::sizeofs::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LeaveReason {
|
||||
Unknown = 0,
|
||||
Surrender = 1,
|
||||
Leave = 2,
|
||||
}
|
||||
|
||||
impl Default for LeaveReason {
|
||||
fn default() -> Self {
|
||||
LeaveReason::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for LeaveReason {
|
||||
fn from(i: i32) -> Self {
|
||||
match i {
|
||||
0 => LeaveReason::Unknown,
|
||||
1 => LeaveReason::Surrender,
|
||||
2 => LeaveReason::Leave,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for LeaveReason {
|
||||
fn from(s: &'a str) -> Self {
|
||||
match s {
|
||||
"Unknown" => LeaveReason::Unknown,
|
||||
"Surrender" => LeaveReason::Surrender,
|
||||
"Leave" => LeaveReason::Leave,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct ReplayChunk<'a> {
|
||||
pub timestamp: i32,
|
||||
pub client_id: i32,
|
||||
pub inner: Option<stormgate::mod_ReplayChunk::Wrapper<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for ReplayChunk<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(8) => msg.timestamp = r.read_int32(bytes)?,
|
||||
Ok(16) => msg.client_id = r.read_int32(bytes)?,
|
||||
Ok(26) => msg.inner = Some(r.read_message::<stormgate::mod_ReplayChunk::Wrapper>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for ReplayChunk<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.timestamp == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.timestamp) as u64) }
|
||||
+ if self.client_id == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.client_id) as u64) }
|
||||
+ self.inner.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.timestamp != 0i32 { w.write_with_tag(8, |w| w.write_int32(*&self.timestamp))?; }
|
||||
if self.client_id != 0i32 { w.write_with_tag(16, |w| w.write_int32(*&self.client_id))?; }
|
||||
if let Some(ref s) = self.inner { w.write_with_tag(26, |w| w.write_message(s))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_ReplayChunk {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct Wrapper<'a> {
|
||||
pub content: Option<stormgate::mod_ReplayChunk::mod_Wrapper::ReplayContent<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for Wrapper<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.content = Some(r.read_message::<stormgate::mod_ReplayChunk::mod_Wrapper::ReplayContent>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for Wrapper<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ self.content.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if let Some(ref s) = self.content { w.write_with_tag(10, |w| w.write_message(s))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_Wrapper {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct ReplayContent<'a> {
|
||||
pub content_type: stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type<'a>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for ReplayContent<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(26) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::map(r.read_message::<stormgate::Map>(bytes)?),
|
||||
Ok(98) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player(r.read_message::<stormgate::Player>(bytes)?),
|
||||
Ok(106) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::change_slot(r.read_message::<stormgate::LobbyChangeSlot>(bytes)?),
|
||||
Ok(122) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::set_variable(r.read_message::<stormgate::LobbySetVariable>(bytes)?),
|
||||
Ok(146) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::start_game(r.read_message::<stormgate::StartGame>(bytes)?),
|
||||
Ok(202) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player_left_game(r.read_message::<stormgate::PlayerLeftGame>(bytes)?),
|
||||
Ok(298) => msg.content_type = stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::assign_player_slot(r.read_message::<stormgate::AssignPlayerSlot>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for ReplayContent<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ match self.content_type {
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::map(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::change_slot(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::set_variable(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::start_game(ref m) => 2 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player_left_game(ref m) => 2 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::assign_player_slot(ref m) => 2 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::None => 0,
|
||||
} }
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
match self.content_type { stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::map(ref m) => { w.write_with_tag(26, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player(ref m) => { w.write_with_tag(98, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::change_slot(ref m) => { w.write_with_tag(106, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::set_variable(ref m) => { w.write_with_tag(122, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::start_game(ref m) => { w.write_with_tag(146, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::player_left_game(ref m) => { w.write_with_tag(202, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::assign_player_slot(ref m) => { w.write_with_tag(298, |w| w.write_message(m))? },
|
||||
stormgate::mod_ReplayChunk::mod_Wrapper::mod_ReplayContent::OneOfcontent_type::None => {},
|
||||
} Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_ReplayContent {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OneOfcontent_type<'a> {
|
||||
map(stormgate::Map<'a>),
|
||||
player(stormgate::Player<'a>),
|
||||
change_slot(stormgate::LobbyChangeSlot),
|
||||
set_variable(stormgate::LobbySetVariable),
|
||||
start_game(stormgate::StartGame),
|
||||
player_left_game(stormgate::PlayerLeftGame),
|
||||
assign_player_slot(stormgate::AssignPlayerSlot<'a>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> Default for OneOfcontent_type<'a> {
|
||||
fn default() -> Self {
|
||||
OneOfcontent_type::None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct Map<'a> {
|
||||
pub folder: Cow<'a, str>,
|
||||
pub name: Cow<'a, str>,
|
||||
pub seed: i32,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for Map<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.folder = r.read_string(bytes).map(Cow::Borrowed)?,
|
||||
Ok(18) => msg.name = r.read_string(bytes).map(Cow::Borrowed)?,
|
||||
Ok(24) => msg.seed = r.read_int32(bytes)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for Map<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.folder == "" { 0 } else { 1 + sizeof_len((&self.folder).len()) }
|
||||
+ if self.name == "" { 0 } else { 1 + sizeof_len((&self.name).len()) }
|
||||
+ if self.seed == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.seed) as u64) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.folder != "" { w.write_with_tag(10, |w| w.write_string(&**&self.folder))?; }
|
||||
if self.name != "" { w.write_with_tag(18, |w| w.write_string(&**&self.name))?; }
|
||||
if self.seed != 0i32 { w.write_with_tag(24, |w| w.write_int32(*&self.seed))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct Player<'a> {
|
||||
pub uuid: Option<stormgate::UUID>,
|
||||
pub name: Option<stormgate::mod_Player::PlayerName<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for Player<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(18) => msg.uuid = Some(r.read_message::<stormgate::UUID>(bytes)?),
|
||||
Ok(26) => msg.name = Some(r.read_message::<stormgate::mod_Player::PlayerName>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for Player<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ self.uuid.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
+ self.name.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if let Some(ref s) = self.uuid { w.write_with_tag(18, |w| w.write_message(s))?; }
|
||||
if let Some(ref s) = self.name { w.write_with_tag(26, |w| w.write_message(s))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_Player {
|
||||
|
||||
use std::borrow::Cow;
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct PlayerName<'a> {
|
||||
pub nickname: Cow<'a, str>,
|
||||
pub discriminator: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for PlayerName<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.nickname = r.read_string(bytes).map(Cow::Borrowed)?,
|
||||
Ok(18) => msg.discriminator = r.read_string(bytes).map(Cow::Borrowed)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for PlayerName<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.nickname == "" { 0 } else { 1 + sizeof_len((&self.nickname).len()) }
|
||||
+ if self.discriminator == "" { 0 } else { 1 + sizeof_len((&self.discriminator).len()) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.nickname != "" { w.write_with_tag(10, |w| w.write_string(&**&self.nickname))?; }
|
||||
if self.discriminator != "" { w.write_with_tag(18, |w| w.write_string(&**&self.discriminator))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct LobbyChangeSlot {
|
||||
pub choice: Option<stormgate::mod_LobbyChangeSlot::SlotChoice>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for LobbyChangeSlot {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.choice = Some(r.read_message::<stormgate::mod_LobbyChangeSlot::SlotChoice>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for LobbyChangeSlot {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ self.choice.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if let Some(ref s) = self.choice { w.write_with_tag(10, |w| w.write_message(s))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_LobbyChangeSlot {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct SlotChoice {
|
||||
pub choice_type: stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for SlotChoice {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(18) => msg.choice_type = stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type::specific_slot(r.read_message::<stormgate::mod_LobbyChangeSlot::mod_SlotChoice::SpecificSlot>(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for SlotChoice {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ match self.choice_type {
|
||||
stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type::specific_slot(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type::None => 0,
|
||||
} }
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
match self.choice_type { stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type::specific_slot(ref m) => { w.write_with_tag(18, |w| w.write_message(m))? },
|
||||
stormgate::mod_LobbyChangeSlot::mod_SlotChoice::OneOfchoice_type::None => {},
|
||||
} Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_SlotChoice {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct SpecificSlot {
|
||||
pub slot: i32,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for SpecificSlot {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(8) => msg.slot = r.read_int32(bytes)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for SpecificSlot {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.slot == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.slot) as u64) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.slot != 0i32 { w.write_with_tag(8, |w| w.write_int32(*&self.slot))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OneOfchoice_type {
|
||||
specific_slot(stormgate::mod_LobbyChangeSlot::mod_SlotChoice::SpecificSlot),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for OneOfchoice_type {
|
||||
fn default() -> Self {
|
||||
OneOfchoice_type::None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct LobbySetVariable {
|
||||
pub slot: i32,
|
||||
pub variable_id: u32,
|
||||
pub value: u32,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for LobbySetVariable {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(24) => msg.slot = r.read_int32(bytes)?,
|
||||
Ok(32) => msg.variable_id = r.read_uint32(bytes)?,
|
||||
Ok(40) => msg.value = r.read_uint32(bytes)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for LobbySetVariable {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.slot == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.slot) as u64) }
|
||||
+ if self.variable_id == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.variable_id) as u64) }
|
||||
+ if self.value == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.value) as u64) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.slot != 0i32 { w.write_with_tag(24, |w| w.write_int32(*&self.slot))?; }
|
||||
if self.variable_id != 0u32 { w.write_with_tag(32, |w| w.write_uint32(*&self.variable_id))?; }
|
||||
if self.value != 0u32 { w.write_with_tag(40, |w| w.write_uint32(*&self.value))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct StartGame { }
|
||||
|
||||
impl<'a> MessageRead<'a> for StartGame {
|
||||
fn from_reader(r: &mut BytesReader, _: &[u8]) -> Result<Self> {
|
||||
r.read_to_end();
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for StartGame { }
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct PlayerLeftGame {
|
||||
pub player_uuid: Option<stormgate::UUID>,
|
||||
pub reason: stormgate::LeaveReason,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for PlayerLeftGame {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.player_uuid = Some(r.read_message::<stormgate::UUID>(bytes)?),
|
||||
Ok(16) => msg.reason = r.read_enum(bytes)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for PlayerLeftGame {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ self.player_uuid.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
+ if self.reason == stormgate::LeaveReason::Unknown { 0 } else { 1 + sizeof_varint(*(&self.reason) as u64) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if let Some(ref s) = self.player_uuid { w.write_with_tag(10, |w| w.write_message(s))?; }
|
||||
if self.reason != stormgate::LeaveReason::Unknown { w.write_with_tag(16, |w| w.write_enum(*&self.reason as i32))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct AssignPlayerSlot<'a> {
|
||||
pub uuid: Option<stormgate::UUID>,
|
||||
pub slot: i64,
|
||||
pub nickname: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for AssignPlayerSlot<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.uuid = Some(r.read_message::<stormgate::UUID>(bytes)?),
|
||||
Ok(16) => msg.slot = r.read_int64(bytes)?,
|
||||
Ok(26) => msg.nickname = r.read_string(bytes).map(Cow::Borrowed)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for AssignPlayerSlot<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ self.uuid.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
|
||||
+ if self.slot == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.slot) as u64) }
|
||||
+ if self.nickname == "" { 0 } else { 1 + sizeof_len((&self.nickname).len()) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if let Some(ref s) = self.uuid { w.write_with_tag(10, |w| w.write_message(s))?; }
|
||||
if self.slot != 0i64 { w.write_with_tag(16, |w| w.write_int64(*&self.slot))?; }
|
||||
if self.nickname != "" { w.write_with_tag(26, |w| w.write_string(&**&self.nickname))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct UUID {
|
||||
pub part1: i64,
|
||||
pub part2: i64,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for UUID {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(8) => msg.part1 = r.read_int64(bytes)?,
|
||||
Ok(16) => msg.part2 = r.read_int64(bytes)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageWrite for UUID {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.part1 == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.part1) as u64) }
|
||||
+ if self.part2 == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.part2) as u64) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.part1 != 0i64 { w.write_with_tag(8, |w| w.write_int64(*&self.part1))?; }
|
||||
if self.part2 != 0i64 { w.write_with_tag(16, |w| w.write_int64(*&self.part2))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user