1use crate::chat::Chat;
5use crate::continent::Continent;
6use crate::error::{AnyResult, Error, Result};
7use crate::military::Military;
8use crate::npc::bot::BotManager;
9use crate::npc::precursor::PrecursorManager;
10use crate::player::{Player, PlayerManager};
11use crate::ranking::Ranking;
12use crate::report::ReportManager;
13use crate::round::{Round, RoundId};
14use crate::world::config::{WorldConfig, WorldName};
15use crate::world::stats::WorldStats;
16use anyhow::bail;
17use flate2::Compression;
18use flate2::bufread::GzDecoder;
19use flate2::write::GzEncoder;
20use jiff::Zoned;
21use semver::Version;
22use serde::de::DeserializeOwned;
23use serde::{Deserialize, Serialize};
24use std::fmt;
25use std::io::{Read, Write};
26use tar::Archive;
27
28const MINIFY: bool = cfg!(any(
29 not(debug_assertions),
30 target_os = "android",
31 target_os = "ios"
32));
33
34type TarBuilder<'a> = tar::Builder<GzEncoder<&'a mut Vec<u8>>>;
35
36#[derive(Clone, Debug, Deserialize, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct Savedata {
39 pub(crate) round: Round,
40 pub(crate) continent: Continent,
41 pub(crate) player_manager: PlayerManager,
42 pub(crate) bot_manager: BotManager,
43 pub(crate) precursor_manager: PrecursorManager,
44 pub(crate) military: Military,
45 pub(crate) ranking: Ranking,
46 pub(crate) report: ReportManager,
47 pub(crate) chat: Chat,
48
49 pub(crate) config: WorldConfig,
50 pub(crate) stats: WorldStats,
51}
52
53impl Savedata {
54 pub fn read(bytes: &[u8]) -> Result<Self> {
55 read_tar(bytes, "world")
56 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
57 .map_err(|_| Error::FailedToReadSavedata)
58 }
59
60 pub(crate) fn write(self, buffer: &mut Vec<u8>) -> Result<()> {
61 write_tar(buffer, &self)
62 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
63 .map_err(|_| Error::FailedToWriteSavedata)
64 }
65
66 pub fn players(&self) -> impl Iterator<Item = &Player> {
67 self.player_manager.players()
68 }
69}
70
71#[derive(Clone, Debug, Deserialize, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct SavedataInfo {
74 world_name: WorldName,
75 round: RoundId,
76 version: Version,
77 saved_at: Zoned,
78}
79
80impl SavedataInfo {
81 fn new(data: &Savedata) -> AnyResult<Self> {
82 Ok(Self {
83 world_name: data.config.name(),
84 round: data.round.id(),
85 version: Version::parse(env!("CARGO_PKG_VERSION"))?,
86 saved_at: Zoned::now(),
87 })
88 }
89
90 pub fn read(bytes: &[u8]) -> Result<Self> {
91 read_tar(bytes, "info")
92 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
93 .map_err(|_| Error::FailedToReadSavedata)
94 }
95}
96
97fn read_tar<T>(bytes: &[u8], entry_name: &str) -> AnyResult<T>
98where
99 T: DeserializeOwned,
100{
101 let decoder = GzDecoder::new(bytes);
102 let mut archive = Archive::new(decoder);
103
104 for entry in archive.entries()? {
105 let mut entry = entry?;
106 if let Ok(entry_path) = entry.path()
107 && let Some(path) = entry_path.to_str()
108 && path == entry_name
109 {
110 let size = entry.size().try_into()?;
111 let mut buffer = Vec::with_capacity(size);
112 entry.read_to_end(&mut buffer)?;
113 return Ok(serde_json::from_slice(&buffer)?);
114 }
115 }
116
117 bail!("Entry not found: {entry_name}");
118}
119
120fn write_tar(buffer: &mut Vec<u8>, data: &Savedata) -> AnyResult<()> {
121 let encoder = GzEncoder::new(buffer, Compression::best());
122 let mut tar_builder = TarBuilder::new(encoder);
123
124 let info = SavedataInfo::new(data)?;
125 append(&mut tar_builder, &info, "info")?;
126 append(&mut tar_builder, data, "world")?;
127
128 tar_builder.into_inner()?.finish()?.flush()?;
129
130 Ok(())
131}
132
133fn append<T>(builder: &mut TarBuilder, value: &T, path: &str) -> AnyResult<()>
134where
135 T: ?Sized + Serialize,
136{
137 let bytes = if MINIFY {
138 serde_json::to_vec(value)?
139 } else {
140 serde_json::to_vec_pretty(value)?
141 };
142
143 let mut header = tar::Header::new_gnu();
144 header.set_size(bytes.len().try_into()?);
145
146 builder.append_data(&mut header, path, bytes.as_slice())?;
147
148 Ok(())
149}
150
151pub struct SaveHandle(Box<dyn FnOnce(Vec<u8>) + Send + Sync>);
152
153impl SaveHandle {
154 pub fn new<F>(f: F) -> Self
155 where
156 F: FnOnce(Vec<u8>) + Send + Sync + 'static,
157 {
158 Self(Box::new(f))
159 }
160
161 #[inline]
162 pub fn save(self, data: Vec<u8>) {
163 (self.0)(data);
164 }
165}
166
167impl fmt::Debug for SaveHandle {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 f.debug_tuple("SaveHandle")
170 .finish_non_exhaustive()
171 }
172}