Skip to main content

larian_formats/
error.rs

1//! Error types for `larian-formats`.
2
3use std::{fmt::Display, num::ParseIntError, path::PathBuf};
4use thiserror::Error;
5
6/// Convenience type alias for `Result` with the default error type.
7pub type Result<T, E = Error> = std::result::Result<T, E>;
8
9/// Errors that can occur when processing BG3 files.
10#[derive(Debug, Error)]
11pub enum Error {
12    /// The parent index specified in the .lsf file was not found when parsing the file.
13    #[error("parent index {index} in .lsf file not found when parsing {context}")]
14    InvalidParentIndex { context: String, index: u32 },
15
16    /// The compression type specified in the file is not supported.
17    #[error("unknown compression type specified with {value} found when {context}")]
18    UnknownCompressionType { value: u32, context: String },
19
20    // The offset to the compressed file didn't correctly point to a location in the file.
21    #[error(
22        "compressed file offset {compressed_file_offset_found} doesn't specify a valid location \
23         in the file"
24    )]
25    InvalidCompressedFileOffset { compressed_file_offset_found: u64 },
26
27    /// The footer that should contain metadata did not properly indicate its length.
28    #[error(
29         "{} bytes found after footer offset {footer_offset_found}, but the footer indicated a length of {footer_length_found}",
30         num_bytes - footer_offset_found
31     )]
32    InvalidFooterLength {
33        footer_length_found: usize,
34        footer_offset_found: usize,
35        num_bytes: usize,
36    },
37
38    /// The offset to the footer didn't correctly point to a location in the file.
39    #[error("footer offset {footer_offset_found} doesn't specify a valid location in the file")]
40    InvalidFooterOffset { footer_offset_found: u64 },
41
42    /// Unable to decompress a file.
43    #[error(
44        "tried to LZ4 decompress {context} with compressed_length 0x{compressed_length:x} and \
45         decompressed length 0x{decompressed_length:x} but got {error}"
46    )]
47    Lz4DecompressionFailed {
48        context: String,
49        error: lz4_flex::block::DecompressError,
50        compressed_length: usize,
51        decompressed_length: usize,
52    },
53
54    /// The number of entries in the file is too high to fit into a the given integer width.
55    #[error(
56        "during {context} got {value} entries which is too many to fit into a {bit_width}-bit \
57         integer"
58    )]
59    NumberOfEntriesTooHigh {
60        context: String,
61        value: u64,
62        bit_width: u32,
63    },
64
65    /// The compressed size of the file is too large to fit into a the given integer width.
66    #[error(
67        "during {context} got {value} bytes which is too large to fit into a {bit_width}-bit \
68         integer"
69    )]
70    CompressedFileSizeTooHigh {
71        context: String,
72        value: u64,
73        bit_width: u32,
74    },
75
76    /// The decompressed size of the file is too large to fit into a the given integer width.
77    #[error(
78        "during {context} got {value} bytes which is too large to fit into a {bit_width}-bit \
79         integer"
80    )]
81    DecompressedFileSizeTooHigh {
82        context: String,
83        value: u64,
84        bit_width: u32,
85    },
86
87    /// The compressed size of the metadata is too large to fit into a the given integer width.
88    #[error(
89        "during {context} got metadata length of {value} bytes which is too large to fit into a \
90         {bit_width}-bit integer"
91    )]
92    CompressedMetadataSizeTooHigh {
93        context: String,
94        value: u64,
95        bit_width: u32,
96    },
97
98    /// The length of
99    #[error("string length {value} for {context} does not fit into {bit_width} bits")]
100    StringLengthTooHigh {
101        context: String,
102        value: u64,
103        bit_width: u32,
104    },
105
106    /// The index of the entry is too large to fit into the given integer width.
107    #[error(
108        "during {context} got index {value} which is too large to fit into a signed \
109         {bit_width}-bit integer"
110    )]
111    IndexTooHigh {
112        context: String,
113        value: u32,
114        bit_width: u32,
115    },
116
117    /// The "magic number" used to identify the type of file didn't match the expected value.
118    #[error("found {bytes_found:?} instead of {bytes_found:?}")]
119    InvalidIdentifier {
120        expected_bytes: [u8; 4],
121        bytes_found: [u8; 4],
122    },
123
124    /// The number of attributes in the mod info section doesn't match the expected count of 6.
125    #[error("mod info had {found} attributes instead of 6")]
126    InvalidModInfoAttributeCount { found: usize },
127
128    /// A meta.lsx file was found within an LSPK .pak archive that didn't match the expected
129    /// XML format.
130    #[error("meta.lsx in the LSPK file didn't match expected schema due to {context}")]
131    InvalidMetadataFile { context: String },
132
133    /// The mod info section is missing the `"Folder"` attribute.
134    #[error("mod info missing folder attribute")]
135    MissingModInfoFolder,
136
137    /// The mod info section is missing the `"MD5"` attribute.
138    #[error("mod info missing md5 attribute")]
139    MissingModInfoMd5,
140
141    /// The mod info section is missing the `"Name"` attribute.
142    #[error("mod info missing name attribute")]
143    MissingModInfoName,
144
145    /// The mod info section is missing the `"PublishHandle"` attribute.
146    #[error("mod info missing publish_handle attribute")]
147    MissingModInfoPublishHandle,
148
149    /// The mod info section is missing the `"UUID"` attribute.
150    #[error("mod info missing uuid attribute")]
151    MissingModInfoUuid,
152
153    /// The mod info section is missing the `"Version"` attribute.
154    #[error("mod info missing version attribute")]
155    MissingModInfoVersion,
156
157    /// An I/O error occured.
158    #[error("when {context} got {inner}")]
159    Io {
160        context: String,
161        inner: std::io::Error,
162    },
163
164    /// The attribute with the given name was not found.
165    #[error("no value found for attribute {name}")]
166    MissingAttributeValue { name: &'static str },
167
168    /// No meta.lsx file was found in an LSPK .pak file.
169    /// A zipfile didn't contain any file with the .pak extension.
170    #[error("no file with the correct extension was found in the zip")]
171    MissingLspkFileInZip,
172
173    /// No meta.lsx file was found in the LSPK file.
174    #[error("no meta.lsx was found in the LSPK file")]
175    MissingMetadataFile,
176
177    /// The LSX file is missing the required `"ModuleInfo"` node.
178    #[error(
179        "no node found with XPath \
180         /save/region[@id='Config']/node[@id='root']/children/node[@id='ModuleInfo']"
181    )]
182    MissingModuleInfo,
183
184    /// The an integer value could not be parsed from the LSX file at the given location.
185    #[error("could not parse {location} as integer because {inner}")]
186    ParseIntError {
187        inner: ParseIntError,
188        location: &'static str,
189    },
190
191    /// An error occurred when trying to parse the path of a file within an LSPK .pak archive.
192    #[error("tried to decompress table but got {0}")]
193    TableEntryDecompressionFailed(lz4_flex::block::DecompressError),
194
195    /// The file being read was too short to contain all of the expected metadata of the given
196    /// format.
197    #[error("not enough bytes to be a valid file of the given format")]
198    TooShort,
199
200    /// The version number read when parsing the file was lower than the minimum supported version.
201    #[error("this tool does not support version number {version_number_found}")]
202    UnsupportedVersion { version_number_found: u32 },
203
204    /// The path isn't supported for packing from.
205    #[error("can't pack mods at path `{0}`; try moving them to a named directory before packing")]
206    UnsupportedModPath(PathBuf),
207
208    /// An error occured when trying to read the data from a zipfile.
209    #[cfg(feature = "zip")]
210    #[error("{0}")]
211    UnableToReadZipFile(#[from] rc_zip_sync::rc_zip::error::Error),
212
213    /// Unable to decompress a file.
214    #[error("tried to decompress {} but got {error}", path.display())]
215    ZlibDecompressionFailed {
216        path: PathBuf,
217        error: std::io::Error,
218    },
219
220    #[error("tried to decompress {} but got {error}", path.display())]
221    ZstdDecompressionFailed { path: PathBuf, error: String },
222
223    /// The file contains zstd-compressed data, but this tool does not currently support it.
224    #[error("zstd-compressed data in .pak not currently supported")]
225    ZstdNotSupported,
226
227    /// The LSX data could not be parsed.
228    #[error("failed to deserialize XML: {0}")]
229    XmlDeserialize(#[from] quick_xml::DeError),
230
231    /// The LSX data could not be serialized.
232    #[error("failed to serialize XML: {0}")]
233    XmlSerialize(#[from] quick_xml::SeError),
234
235    /// The LSX data could not be written to a file.
236    #[error("failed to write XML: {0}")]
237    XmlWrite(std::io::Error),
238
239    /// LSF attribute is missing a required field.
240    #[error("LSF XML attribute '{attribute_id}': missing required field '{field}'")]
241    LsfXmlMissingField {
242        attribute_id: String,
243        field: &'static str,
244    },
245
246    /// LSF value has an unknown type.
247    #[error("LSF XML attribute '{attribute_id}': unknown type '{type_name}'")]
248    LsfXmlUnknownType {
249        attribute_id: String,
250        type_name: String,
251    },
252
253    /// LSF value has the wrong number of components.
254    #[error(
255        "LSF XML attribute '{attribute_id}': expected {expected} space-separated values but got \
256         {found}"
257    )]
258    LsfXmlWrongComponentCount {
259        attribute_id: String,
260        expected: usize,
261        found: usize,
262    },
263
264    /// LSF value failed to parse.
265    #[error(
266        "failed to parse LSF XML `{attribute_id}` attribute `{value}` as {expected_type} due to \
267         {inner}"
268    )]
269    LsfXmlParse {
270        attribute_id: String,
271        expected_type: String,
272        value: String,
273        inner: String,
274    },
275
276    /// LSF value failed to decode base64.
277    #[error("LSF XML attribute '{attribute_id}': failed to decode base64: {inner}")]
278    LsfXmlDecodeBase64 {
279        attribute_id: String,
280        inner: base64::DecodeError,
281    },
282}
283
284impl Error {
285    pub fn custom_io(context: impl Display, inner: std::io::Error) -> Self {
286        Self::Io {
287            context: format!("{context}"),
288            inner,
289        }
290    }
291
292    pub fn read_io(context: impl Display, inner: std::io::Error) -> Self {
293        Self::Io {
294            context: format!("reading {context}"),
295            inner,
296        }
297    }
298
299    pub fn write_io(context: impl Display, inner: std::io::Error) -> Self {
300        Self::Io {
301            context: format!("writing {context}"),
302            inner,
303        }
304    }
305}