sims_far/
lib.rs

1#![crate_name = "sims_far"]
2
3use std::convert::Infallible;
4use std::fs::File;
5use std::io;
6use std::io::SeekFrom::Start;
7use std::io::{Read, Seek};
8use std::str::{from_utf8, Utf8Error};
9use thiserror::Error;
10
11#[derive(Error, Debug)]
12pub enum FarError {
13    #[error("File error: {0}")]
14    FileError(#[from] io::Error),
15    #[error("utf8 error: {0}")]
16    Utf8Error(#[from] Utf8Error),
17    #[error("infallible error: {0}")]
18    InfallibleError(#[from] Infallible),
19}
20
21/// The FAR format (.far files) are used to bundle (archive) multiple files together. All numeric
22/// values in the header and manifest are stored in little-endian order(least significant byte
23/// first).
24#[derive(Clone)]
25pub struct Far {
26    /// The signature is an eight-byte string, consisting literally of "FAR!byAZ" (without the
27    /// quotes).
28    pub signature: String,
29    /// The version is always one.
30    pub version: u32,
31    /// The manifest offset is the byte offset from the beginning of the file to the manifest.
32    /// The contents of the archived files are simply concatenated together without any other
33    /// structure or padding.Caveat: all of the files observed have been a multiple of four in
34    /// length, so it's possible that the files may be padded to a two-byte or four-byte boundary
35    /// and the case has simply never been encountered.
36    pub manifest_offset: u32,
37    /// The manifest contains a count of the number of archived files, followed by an entry for
38    /// each file. In all of the examples examined the order of the entries matches the order of
39    /// the archived files, but whether this is a firm requirement or not is unknown.
40    pub manifest: Manifest,
41}
42
43impl Far {
44    /// Create a new instance of Far and parse it
45    pub fn new(path: &str) -> Result<Far, FarError> {
46        return parse_far(path);
47    }
48}
49
50/// The manifest contains a count of the number of archived files, followed by an entry for each
51/// file. In all of the examples examined the order of the entries matches the order of the archived
52/// files, but whether this is a firm requirement or not is unknown.
53#[derive(Clone)]
54pub struct Manifest {
55    /// The number of files in the far file.
56    pub number_of_files: u32,
57    /// A list of Manifest Entries in the far file.
58    pub manifest_entries: Vec<ManifestEntry>,
59}
60
61/// A manifest entry containing the first file length, second file length, file offset, file name
62/// length, and file name.
63#[derive(Clone)]
64pub struct ManifestEntry {
65    file_path: String,
66    /// The file length is stored twice. Perhaps this is because some variant of FAR files supports
67    /// compressed data and the fields would hold the compressed and uncompressed sizes, but this is
68    /// pure speculation. The safest thing to do is to leave the fields identical.
69    pub file_length1: u32,
70    /// The file length is stored twice. Perhaps this is because some variant of FAR files supports
71    /// compressed data and the fields would hold the compressed and uncompressed sizes, but this is
72    /// pure speculation. The safest thing to do is to leave the fields identical.
73    pub file_length2: u32,
74    /// The file offset is the byte offset from the beginning of the FAR file to the archived file.
75    pub file_offset: u32,
76    /// The filename length is the number of bytes in the filename. Filenames are stored without a
77    /// terminating null. For example, the filename "foo" would have a filename length of three and
78    /// the entry would be nineteen bytes long in total.
79    pub file_name_length: u32,
80    /// The name of the file. This can include directories.
81    pub file_name: String,
82}
83
84impl ManifestEntry {
85    pub fn get_bytes(&self) -> Result<Vec<u8>, FarError> {
86        let mut f = File::open(self.file_path.as_str())?;
87        let mut buf: Vec<u8> = vec![0x00; self.file_length1 as usize];
88        f.seek(Start(self.file_offset as u64))?;
89        f.read_exact(&mut *buf)?;
90        return Ok(buf);
91    }
92}
93
94fn parse_far(path: &str) -> Result<Far, FarError> {
95    let mut far = Far {
96        signature: "".to_string(),
97        version: 0,
98        manifest_offset: 0,
99        manifest: Manifest {
100            number_of_files: 0,
101            manifest_entries: vec![],
102        },
103    };
104
105    let mut f = File::open(path)?;
106
107    // read signature
108    let mut buf: [u8; 8] = [0x00; 8];
109    f.read_exact(&mut buf)?;
110    far.signature = from_utf8(&buf)?.to_string();
111
112    // read version
113    let mut buf: [u8; 4] = [0x00; 4];
114    f.read_exact(&mut buf)?;
115    far.version = u32::from_le_bytes(buf.try_into()?);
116
117    // read manifest offset
118    f.read_exact(&mut buf)?;
119    far.manifest_offset = u32::from_le_bytes(buf.try_into()?);
120
121    // read manifest
122    f.seek(Start(far.manifest_offset as u64))?;
123    f.read_exact(&mut buf)?;
124    far.manifest.number_of_files = u32::from_le_bytes(buf.try_into()?);
125
126    // read manifest entries
127    for _ in 0..far.manifest.number_of_files {
128        let me = parse_manifest_entry(&mut f, path)?;
129        far.manifest.manifest_entries.push(me);
130    }
131
132    return Ok(far);
133}
134
135fn parse_manifest_entry(f: &mut File, uigraphics_path: &str) -> Result<ManifestEntry, FarError> {
136    let mut me = ManifestEntry {
137        file_path: uigraphics_path.to_string(),
138        file_length1: 0,
139        file_length2: 0,
140        file_offset: 0,
141        file_name_length: 0,
142        file_name: "".to_string(),
143    };
144    let mut buf: [u8; 4] = [0x00; 4];
145
146    // read file length 1
147    f.read_exact(&mut buf)?;
148    me.file_length1 = u32::from_le_bytes(buf.try_into()?);
149
150    // read file length 2
151    f.read_exact(&mut buf)?;
152    me.file_length2 = u32::from_le_bytes(buf.try_into()?);
153
154    // read file offset
155    f.read_exact(&mut buf)?;
156    me.file_offset = u32::from_le_bytes(buf.try_into()?);
157
158    // read file name length
159    f.read_exact(&mut buf)?;
160    me.file_name_length = u32::from_le_bytes(buf.try_into()?);
161
162    // read file name
163    let mut buf: Vec<u8> = vec![0x00; me.file_name_length as usize];
164    f.read_exact(&mut buf)?;
165    me.file_name = from_utf8(&buf)?.to_string();
166
167    return Ok(me);
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_new() {
176        let path = r"test.far";
177        let far = Far::new(path).unwrap();
178        assert_eq!(far.signature, "FAR!byAZ");
179        assert_eq!(far.version, 1);
180        assert_eq!(far.manifest_offset, 160);
181        assert_eq!(far.manifest.number_of_files, 1);
182        assert_eq!(far.manifest.manifest_entries[0].file_length1, 144);
183        assert_eq!(far.manifest.manifest_entries[0].file_length2, 144);
184        assert_eq!(far.manifest.manifest_entries[0].file_offset, 16);
185        assert_eq!(far.manifest.manifest_entries[0].file_name_length, 8);
186        assert_eq!(far.manifest.manifest_entries[0].file_name, "test.bmp");
187    }
188
189    #[test]
190    fn test_get_bytes() {
191        let path = r"test.far";
192        let far = Far::new(path).unwrap();
193        assert_eq!(
194            far.manifest.manifest_entries[0]
195                .get_bytes()
196                .expect("bad")
197                .len(),
198            144
199        );
200    }
201}