z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

raiguardread_exact was erroring sometimes1cbbe7f

refactor
3.2 KiB115 linesraw
1use anyhow::Result;
2use byteorder::{LittleEndian, ReadBytesExt};
3use compress::zlib;
4use semver::Version;
5use std::fs::File;
6use std::io::prelude::*;
7use std::io::Cursor;
8use std::io::Read;
9use std::io::SeekFrom;
10use std::path::PathBuf;
11use thiserror::Error;
12use zip::ZipArchive;
13
14use crate::types::ModIdent;
15
16const READ_SIZE: usize = 1_048_576;
17
18pub struct SaveFile {
19    pub map_version: Version,
20    pub mods: Vec<ModIdent>,
21    pub path: PathBuf,
22    pub scenario_mod_name: String,
23    pub scenario: String,
24}
25
26impl SaveFile {
27    pub fn from(path: PathBuf) -> Result<Self> {
28        println!("Reading save file...");
29        let mut archive = ZipArchive::new(File::open(&path)?)?;
30        let mut compressed = true;
31        let filenames: Vec<&str> = archive.file_names().collect();
32        let filename = filenames
33            .iter()
34            .find(|filename| filename.contains("level.dat0"))
35            .or_else(|| {
36                compressed = false;
37                filenames
38                    .iter()
39                    .find(|filename| filename.contains("level.dat"))
40            })
41            .map(ToString::to_string)
42            .ok_or(SaveFileErr::NoLevelDat)?;
43        let file = archive.by_name(&filename)?;
44
45        let decompressed = if compressed {
46            let mut bytes = Vec::with_capacity(READ_SIZE);
47            zlib::Decoder::new(file).read_to_end(&mut bytes)?;
48            bytes
49        } else {
50            file.bytes()
51                .take(READ_SIZE)
52                .filter_map(|byte| byte.ok())
53                .collect()
54        };
55
56        let mut cursor = Cursor::new(decompressed);
57        let version_major = cursor.read_u16::<LittleEndian>()?;
58        let version_minor = cursor.read_u16::<LittleEndian>()?;
59        let version_patch = cursor.read_u16::<LittleEndian>()?;
60        let _version_build = cursor.read_u16::<LittleEndian>()?;
61
62        cursor.seek(SeekFrom::Current(2))?;
63
64        let scenario_name = read_string(&mut cursor)?;
65        let scenario_mod_name = read_string(&mut cursor)?;
66
67        // TODO: Handle campaigns
68        cursor.seek(SeekFrom::Current(14))?;
69
70        let num_mods = cursor.read_u8()?;
71
72        let mut mods = Vec::with_capacity(num_mods as usize);
73        for _ in 0..num_mods {
74            mods.push(read_mod(&mut cursor)?);
75        }
76
77        Ok(Self {
78            mods,
79            map_version: Version::new(
80                version_major as u64,
81                version_minor as u64,
82                version_patch as u64,
83            ),
84            path,
85            scenario: scenario_name,
86            scenario_mod_name,
87        })
88    }
89}
90
91#[derive(Debug, Error)]
92pub enum SaveFileErr {
93    #[error("No level.dat was found in the save file")]
94    NoLevelDat,
95}
96
97fn read_string(reader: &mut Cursor<Vec<u8>>) -> Result<String> {
98    let scenario_len = reader.read_u8()?;
99    let mut scenario_name = vec![0; scenario_len as usize];
100    reader.read_exact(&mut scenario_name)?;
101
102    Ok(String::from_utf8_lossy(&scenario_name).to_string())
103}
104
105fn read_mod(reader: &mut Cursor<Vec<u8>>) -> Result<ModIdent> {
106    let mod_name = read_string(reader)?;
107
108    // TODO: Read mod versions
109    reader.seek(SeekFrom::Current(7))?;
110
111    Ok(ModIdent {
112        name: mod_name,
113        version_req: None,
114    })
115}