1use std::io::{self, Read, Seek, SeekFrom, Write};
2
3use nwnrs_io::prelude::*;
4use tracing::{debug, instrument};
5
6use crate::{
7 ENTRY_DATA_SIZE, HEADER_MAGIC, HEADER_VERSION, TABLE_OFFSET, decode_resref, prelude::*,
8};
9
10#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: SsfResult<SsfRoot> =
loop {};
return __tracing_attr_fake_return;
}
{
let file_type = read_str_or_err(reader, 4)?;
expect(file_type == HEADER_MAGIC,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected {0:?}, got {1:?}",
HEADER_MAGIC, file_type))
})).map_err(invalid_data)?;
let file_version = read_str_or_err(reader, 4)?;
expect(file_version == HEADER_VERSION,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected {0:?}, got {1:?}",
HEADER_VERSION, file_version))
})).map_err(invalid_data)?;
let entry_count = read_u32(reader)? as usize;
let table_offset = read_u32(reader)?;
expect(table_offset == TABLE_OFFSET,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected table offset {0}, got {1}",
TABLE_OFFSET, table_offset))
})).map_err(invalid_data)?;
let padding = read_bytes_or_err(reader, 24)?;
expect(padding.iter().all(|byte| *byte == 0),
"expected 24 bytes of zero padding").map_err(invalid_data)?;
let entry_offsets =
read_fixed_count_seq(reader, entry_count,
|_, reader| read_u32(reader))?;
let entries =
read_fixed_count_seq(reader, entry_count,
|idx, reader|
{
let offset =
entry_offsets.get(idx).copied().ok_or_else(||
invalid_message("SSF entry offset index out of range"))?;
reader.seek(SeekFrom::Start(u64::from(offset)))?;
let raw_resref = read_bytes_or_err(reader, 16)?;
let strref = read_u32(reader)?;
let mut raw_resref_bytes = [0_u8; 16];
raw_resref_bytes.copy_from_slice(&raw_resref);
Ok(SsfEntry {
raw_resref: raw_resref_bytes,
resref: decode_resref(&raw_resref),
strref,
})
})?;
let root = SsfRoot { entries };
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/io.rs:69",
"nwnrs_ssf::io", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("src/io.rs"),
::tracing_core::__macro_support::Option::Some(69u32),
::tracing_core::__macro_support::Option::Some("nwnrs_ssf::io"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("entry_count")
}> =
::tracing::__macro_support::FieldName::new("entry_count");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("read ssf")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&root.entries.len()
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Ok(root)
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/io.rs:16",
"nwnrs_ssf::io", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/io.rs"),
::tracing_core::__macro_support::Option::Some(16u32),
::tracing_core::__macro_support::Option::Some("nwnrs_ssf::io"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err)]
17pub fn read_ssf<R: Read + Seek>(reader: &mut R) -> SsfResult<SsfRoot> {
18 let file_type = read_str_or_err(reader, 4)?;
19 expect(
20 file_type == HEADER_MAGIC,
21 format!("expected {HEADER_MAGIC:?}, got {file_type:?}"),
22 )
23 .map_err(invalid_data)?;
24
25 let file_version = read_str_or_err(reader, 4)?;
26 expect(
27 file_version == HEADER_VERSION,
28 format!("expected {HEADER_VERSION:?}, got {file_version:?}"),
29 )
30 .map_err(invalid_data)?;
31
32 let entry_count = read_u32(reader)? as usize;
33 let table_offset = read_u32(reader)?;
34 expect(
35 table_offset == TABLE_OFFSET,
36 format!("expected table offset {TABLE_OFFSET}, got {table_offset}"),
37 )
38 .map_err(invalid_data)?;
39
40 let padding = read_bytes_or_err(reader, 24)?;
41 expect(
42 padding.iter().all(|byte| *byte == 0),
43 "expected 24 bytes of zero padding",
44 )
45 .map_err(invalid_data)?;
46
47 let entry_offsets = read_fixed_count_seq(reader, entry_count, |_, reader| read_u32(reader))?;
48 let entries = read_fixed_count_seq(reader, entry_count, |idx, reader| {
49 let offset = entry_offsets
50 .get(idx)
51 .copied()
52 .ok_or_else(|| invalid_message("SSF entry offset index out of range"))?;
53 reader.seek(SeekFrom::Start(u64::from(offset)))?;
54 let raw_resref = read_bytes_or_err(reader, 16)?;
55 let strref = read_u32(reader)?;
56 let mut raw_resref_bytes = [0_u8; 16];
57 raw_resref_bytes.copy_from_slice(&raw_resref);
58
59 Ok(SsfEntry {
60 raw_resref: raw_resref_bytes,
61 resref: decode_resref(&raw_resref),
62 strref,
63 })
64 })?;
65
66 let root = SsfRoot {
67 entries,
68 };
69 debug!(entry_count = root.entries.len(), "read ssf");
70 Ok(root)
71}
72
73#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: SsfResult<()> = loop {};
return __tracing_attr_fake_return;
}
{
writer.write_all(HEADER_MAGIC.as_bytes())?;
writer.write_all(HEADER_VERSION.as_bytes())?;
writer.write_all(&to_u32(ssf.entries.len(),
"SSF entry count")?.to_le_bytes())?;
writer.write_all(&TABLE_OFFSET.to_le_bytes())?;
writer.write_all(&[0_u8; 24])?;
for (idx, _) in ssf.entries.iter().enumerate() {
let table_offset =
usize::try_from(TABLE_OFFSET).map_err(|_error|
invalid_message("SSF table offset exceeds usize"))?;
let offset =
ssf.entries.len().checked_mul(4).and_then(|value|
value.checked_add(table_offset)).and_then(|value|
value.checked_add(idx.saturating_mul(ENTRY_DATA_SIZE))).ok_or_else(||
invalid_message("SSF entry offset overflow"))?;
let offset = to_u32(offset, "SSF entry offset")?;
writer.write_all(&offset.to_le_bytes())?;
}
for entry in &ssf.entries {
writer.write_all(&entry.stored_resref_bytes()?)?;
writer.write_all(&entry.strref.to_le_bytes())?;
}
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/io.rs:106",
"nwnrs_ssf::io", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("src/io.rs"),
::tracing_core::__macro_support::Option::Some(106u32),
::tracing_core::__macro_support::Option::Some("nwnrs_ssf::io"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("entry_count")
}> =
::tracing::__macro_support::FieldName::new("entry_count");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("wrote ssf")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&ssf.entries.len()
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Ok(())
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/io.rs:79",
"nwnrs_ssf::io", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/io.rs"),
::tracing_core::__macro_support::Option::Some(79u32),
::tracing_core::__macro_support::Option::Some("nwnrs_ssf::io"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(entry_count = ssf.entries.len()))]
80pub fn write_ssf<W: Write>(writer: &mut W, ssf: &SsfRoot) -> SsfResult<()> {
81 writer.write_all(HEADER_MAGIC.as_bytes())?;
82 writer.write_all(HEADER_VERSION.as_bytes())?;
83 writer.write_all(&to_u32(ssf.entries.len(), "SSF entry count")?.to_le_bytes())?;
84 writer.write_all(&TABLE_OFFSET.to_le_bytes())?;
85 writer.write_all(&[0_u8; 24])?;
86
87 for (idx, _) in ssf.entries.iter().enumerate() {
88 let table_offset = usize::try_from(TABLE_OFFSET)
89 .map_err(|_error| invalid_message("SSF table offset exceeds usize"))?;
90 let offset = ssf
91 .entries
92 .len()
93 .checked_mul(4)
94 .and_then(|value| value.checked_add(table_offset))
95 .and_then(|value| value.checked_add(idx.saturating_mul(ENTRY_DATA_SIZE)))
96 .ok_or_else(|| invalid_message("SSF entry offset overflow"))?;
97 let offset = to_u32(offset, "SSF entry offset")?;
98 writer.write_all(&offset.to_le_bytes())?;
99 }
100
101 for entry in &ssf.entries {
102 writer.write_all(&entry.stored_resref_bytes()?)?;
103 writer.write_all(&entry.strref.to_le_bytes())?;
104 }
105
106 debug!(entry_count = ssf.entries.len(), "wrote ssf");
107 Ok(())
108}
109
110fn read_u32<R: Read>(reader: &mut R) -> io::Result<u32> {
111 let mut bytes = [0_u8; 4];
112 reader.read_exact(&mut bytes)?;
113 Ok(u32::from_le_bytes(bytes))
114}
115
116fn invalid_data(error: impl std::error::Error + Send + Sync + 'static) -> io::Error {
117 io::Error::new(io::ErrorKind::InvalidData, error)
118}
119
120fn invalid_message(message: impl Into<String>) -> io::Error {
121 io::Error::new(io::ErrorKind::InvalidData, message.into())
122}
123
124fn to_u32(value: usize, what: &str) -> io::Result<u32> {
125 u32::try_from(value).map_err(|_error| invalid_message(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} exceeds 32-bit range", what))
})format!("{what} exceeds 32-bit range")))
126}
127
128#[allow(clippy::panic)]
129#[cfg(test)]
130mod tests {
131 use std::io::Cursor;
132
133 use crate::{SsfEntry, SsfRoot, read_ssf, write_ssf};
134
135 #[test]
136 fn ssf_preserves_raw_resref_bytes_when_only_strref_changes() {
137 let mut original = Vec::new();
138 original.extend_from_slice(b"SSF ");
139 original.extend_from_slice(b"V1.0");
140 original.extend_from_slice(&1_u32.to_le_bytes());
141 original.extend_from_slice(&40_u32.to_le_bytes());
142 original.extend_from_slice(&[0_u8; 24]);
143 original.extend_from_slice(&44_u32.to_le_bytes());
144 let mut raw = [0_u8; 16];
145 if let Some(prefix) = raw.get_mut(..5) {
146 prefix.copy_from_slice(b"HELLO");
147 } else {
148 panic!("fixture resref slice should be in bounds");
149 }
150 original.extend_from_slice(&raw);
151 original.extend_from_slice(&7_u32.to_le_bytes());
152
153 let mut cursor = Cursor::new(original.clone());
154 let mut ssf = match read_ssf(&mut cursor) {
155 Ok(ssf) => ssf,
156 Err(error) => panic!("read ssf: {error}"),
157 };
158 if let Some(entry) = ssf.entries.get_mut(0) {
159 entry.strref = 9;
160 } else {
161 panic!("fixture should contain one SSF entry");
162 }
163
164 let mut encoded = Vec::new();
165 if let Err(error) = write_ssf(&mut encoded, &ssf) {
166 panic!("write ssf: {error}");
167 }
168
169 assert_eq!(
170 encoded.get(..44),
171 original.get(..44),
172 "header prefix should exist"
173 );
174 assert_eq!(
175 encoded.get(44..60),
176 original.get(44..60),
177 "resref should exist"
178 );
179 assert_eq!(encoded.get(60..64), Some(&9_u32.to_le_bytes()[..]));
180 }
181
182 #[test]
183 fn ssf_new_entry_uses_canonical_padding() {
184 let mut ssf = SsfRoot::new();
185 ssf.entries.push(SsfEntry::new("hello", 7));
186
187 let mut encoded = Vec::new();
188 if let Err(error) = write_ssf(&mut encoded, &ssf) {
189 panic!("write ssf: {error}");
190 }
191
192 assert_eq!(encoded.get(44..49), Some(&b"hello"[..]));
193 assert!(
194 encoded
195 .get(49..60)
196 .unwrap_or(&[])
197 .iter()
198 .all(|byte| *byte == 0)
199 );
200 }
201}