Skip to main content

fallout_core/fallout2/
header.rs

1use std::io::{self, Read, Seek};
2
3use crate::reader::BigEndianReader;
4
5use super::types::{HEADER_PADDING, PREVIEW_SIZE, SIGNATURE};
6
7#[derive(Debug)]
8pub struct SaveHeader {
9    pub character_name: String,
10    pub description: String,
11    pub version_major: i16,
12    pub version_minor: i16,
13    pub version_release: u8,
14    pub file_day: i16,
15    pub file_month: i16,
16    pub file_year: i16,
17    pub file_time: i32,
18    pub game_month: i16,
19    pub game_day: i16,
20    pub game_year: i16,
21    pub game_time: u32,
22    pub elevation: i16,
23    pub map: i16,
24    pub map_filename: String,
25}
26
27impl SaveHeader {
28    pub fn parse<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<Self> {
29        // Signature: 24 bytes, validate first 18
30        let sig_bytes = r.read_bytes(24)?;
31        if &sig_bytes[..SIGNATURE.len()] != SIGNATURE {
32            return Err(io::Error::new(
33                io::ErrorKind::InvalidData,
34                "invalid save file signature",
35            ));
36        }
37
38        // Fallout 2 stores 1.2R as [1, 2, 'R'].
39        let version_minor = r.read_i16()?;
40        let version_major = r.read_i16()?;
41        let version_release = r.read_u8()?;
42
43        if version_minor != 1 || version_major != 2 || version_release != b'R' {
44            return Err(io::Error::new(
45                io::ErrorKind::InvalidData,
46                format!(
47                    "unsupported version: {}.{}{}",
48                    version_major, version_minor, version_release as char
49                ),
50            ));
51        }
52
53        let character_name = r.read_fixed_string(32)?;
54        let description = r.read_fixed_string(30)?;
55
56        // Note: written in order day, month, year.
57        let file_day = r.read_i16()?;
58        let file_month = r.read_i16()?;
59        let file_year = r.read_i16()?;
60        let file_time = r.read_i32()?;
61
62        let game_month = r.read_i16()?;
63        let game_day = r.read_i16()?;
64        let game_year = r.read_i16()?;
65        let game_time = r.read_u32()?;
66
67        let elevation = r.read_i16()?;
68        let map = r.read_i16()?;
69        let map_filename = r.read_fixed_string(16)?;
70
71        // Skip thumbnail and padding.
72        r.skip((PREVIEW_SIZE + HEADER_PADDING) as u64)?;
73
74        Ok(Self {
75            character_name,
76            description,
77            version_major,
78            version_minor,
79            version_release,
80            file_day,
81            file_month,
82            file_year,
83            file_time,
84            game_month,
85            game_day,
86            game_year,
87            game_time,
88            elevation,
89            map,
90            map_filename,
91        })
92    }
93}