1use std::collections::BTreeMap;
2pub use std::{
3 collections::{HashMap, HashSet},
4 rc::Rc,
5};
6
7use itertools::Itertools;
8use serde_json::Value as JSONValue;
9use serde_pickle::{HashableValue, Value as PickleValue};
10
11use crate::{fields::gen_collection, manual_parser::pickle_val_to_json_manual, InterceptFn, Result};
12type Dict<T> = HashMap<String, T>;
13use crate::{
14 battle_results::{Field, FieldType},
15 error::Error,
16 fields::{matches_version, FieldCollection},
17 to_default_if_none, Battle,
18};
19
20
21pub struct DatFileParser {
22 fields: FieldCollection,
25}
26
27struct DatfileFormat {
30 arena_unique_id: String,
31 account_self: Vec<PickleValue>,
32 vehicle_self: HashMap<String, Vec<PickleValue>>,
33
34 common: Vec<PickleValue>,
35 account_all: HashMap<String, Vec<PickleValue>>,
36 vehicle_all: HashMap<String, Vec<PickleValue>>,
37 player_info: HashMap<String, Vec<PickleValue>>,
38}
39
40
41#[derive(Debug)]
42pub enum Intercept {
43 Success(&'static Field, serde_json::Value),
44 NotPresent(&'static Field, serde_json::Value),
45 ManuallyParsed(&'static Field, serde_json::Value),
46 Failed(&'static Field, serde_json::Value, String),
47}
48
49impl Intercept {
50 pub fn original_result(self) -> serde_json::Value {
51 use Intercept::*;
52 match self {
53 Success(_, val) | NotPresent(_, val) | ManuallyParsed(_, val) | Failed(_, val, _) => val,
54 }
55 }
56}
57
58impl DatFileParser {
59 pub fn parse(&self, input: &[u8]) -> Result<Battle> {
61 let root_pickle = utils::load_pickle(input).unwrap();
63
64 let datfile_format = parse_root_pickle(root_pickle).unwrap();
66
67 self.parse_datfile_format(datfile_format, |intercept, _| intercept.original_result())
69 }
70
71 pub fn parse_intercept(&self, input: &[u8], intercept: InterceptFn) -> Result<Battle> {
74 let root_pickle = utils::load_pickle(input).unwrap();
76
77 let datfile_format = parse_root_pickle(root_pickle).unwrap();
79
80 self.parse_datfile_format(datfile_format, intercept)
82 }
83
84 pub fn new() -> Self {
86 Self {
87 fields: gen_collection(),
88 }
89 }
90
91 fn parse_datfile_format(&self, datfile: DatfileFormat, intercept: InterceptFn) -> Result<Battle> {
92 use FieldType::*;
93
94 let arena_unique_id = datfile.arena_unique_id;
95
96 let common = pickle_to_json(&self.fields, Common, datfile.common, intercept)?;
97
98 let account_self = pickle_to_json(&self.fields, AccountSelf, datfile.account_self, intercept)?;
99 let account_self = HashMap::from([(
100 account_self.pointer("/accountDBID").unwrap().to_string(),
101 account_self,
102 )]);
103
104 let vehicle_self = parse_list(&self.fields, VehicleSelf, datfile.vehicle_self, intercept)?;
105 let player_info = parse_list(&self.fields, PlayerInfo, datfile.player_info, intercept)?;
106 let account_all = parse_list(&self.fields, AccountAll, datfile.account_all, intercept)?;
107 let vehicle_all = parse_list(&self.fields, VehicleAll, datfile.vehicle_all, intercept)?;
108
109 Ok(Battle {
110 arena_unique_id,
111 common,
112 player_info,
113 account_all,
114 vehicle_all,
115 vehicle_self,
116 account_self,
117 })
118 }
119}
120
121fn parse_list(
122 fields: &FieldCollection, field_type: FieldType, input: Dict<Vec<PickleValue>>, intercept: InterceptFn,
123) -> Result<Dict<JSONValue>> {
124 input
125 .into_iter()
126 .map(|(key, value)| {
127 pickle_to_json(fields, field_type.clone(), value, intercept).map(|value| (key, value))
128 })
129 .collect()
130}
131
132fn decompress_and_load_pickle(input: &PickleValue) -> Result<PickleValue> {
133 let PickleValue::Bytes(input) = input else { return Err(Error::PickleFormatError) };
134 let decompressed =
135 miniz_oxide::inflate::decompress_to_vec_zlib(input).map_err(|_| Error::DecompressionError)?;
136
137 Ok(serde_pickle::value_from_slice(&decompressed, Default::default())?)
138}
139
140fn parse_root_pickle(root_pickle: PickleValue) -> Result<DatfileFormat> {
141 use PickleValue::*;
142 let Tuple(root_tuple) = root_pickle else { return Err(Error::PickleFormatError) };
144
145 let [_, Tuple(data_tuple)] = root_tuple.as_slice() else { return Err(Error::PickleFormatError) };
148
149 let [I64(arena_unique_id), rest @ ..] = data_tuple.as_slice() else {
150 return Err(Error::PickleFormatError)
151 };
152
153 let Some((List(account_self), Dict(vehicle_self), Tuple(multiple))) = rest.into_iter().map(decompress_and_load_pickle).flatten().next_tuple() else {
154 return Err(Error::PickleFormatError)
155 };
156
157 let Some((List(common), Dict(player_info), Dict(vehicle_all), Dict(account_all))) = multiple.into_iter().next_tuple() else {
158 return Err(Error::PickleFormatError)
159 };
160
161 Ok(DatfileFormat {
162 arena_unique_id: arena_unique_id.to_string(),
163 account_self,
164 common,
165 account_all: to_rust_dict(account_all)?,
166 vehicle_all: to_rust_dict(vehicle_all)?,
167 player_info: to_rust_dict(player_info)?,
168 vehicle_self: to_rust_dict(vehicle_self)?,
169 })
170}
171
172
173fn pickle_to_json(
174 fields: &FieldCollection, field_type: FieldType, value_list: Vec<PickleValue>, intercept: InterceptFn,
175) -> Result<JSONValue> {
176 let mut value_list = value_list.into_iter();
177
178 let Some(PickleValue::I64(checksum)) = value_list.next() else {
183 return Err(Error::OtherError("Value list is empty"))
184 };
185
186 let (iden_list, version) = fields
189 .get_fields_list(checksum)
190 .ok_or_else(|| Error::UnknownChecksum(field_type.to_str(), checksum))?;
191
192 let mut map = HashMap::new();
193 for iden in iden_list {
194 if !matches_version(version, iden) {
195 let value = intercept(
196 Intercept::NotPresent(iden, iden.default.to_json_value()),
197 PickleValue::None,
198 );
199
200 map.insert(iden.name, value);
201 } else {
202 let value = value_list.next().ok_or_else(|| Error::DecompressionError)?;
203
204 map.insert(iden.name, pickle_val_to_json(iden, value, intercept));
205 }
206 }
207
208 assert!(value_list.next().is_none());
209 Ok(serde_json::to_value(map).unwrap())
210}
211
212
213fn pickle_val_to_json(iden: &'static Field, input: PickleValue, intercept: InterceptFn) -> JSONValue {
219 let value = to_default_if_none(iden, input);
220
221 match serde_pickle::from_value(value.clone()) {
222 Ok(json_value) => intercept(Intercept::Success(iden, json_value), value),
223
224 Err(_) => match pickle_val_to_json_manual(value.clone(), iden) {
227 Ok(json_value) => intercept(Intercept::ManuallyParsed(iden, json_value), value),
228 Err((err, json_value)) => intercept(Intercept::Failed(iden, json_value, err.to_string()), value),
229 },
230 }
231}
232
233
234fn to_rust_dict(input: BTreeMap<HashableValue, PickleValue>) -> Result<Dict<Vec<PickleValue>>> {
235 input
236 .into_iter()
237 .map(|(key, value)| match value {
238 PickleValue::List(list) => Ok((key.to_string(), list)),
239 PickleValue::Dict(dict) => {
240 let mut dict_iter = dict.into_iter();
241 let Some((inner_key, PickleValue::List(value))) = dict_iter.next() else { return Err(Error::PickleFormatError) };
242
243 Ok((format!("{} {}", key, inner_key), value))
244 }
245 _ => Err(Error::DecompressionError.into()),
246 })
247 .collect()
248}