1use std::io::{Read, Seek, SeekFrom, Write};
8
9use binrw::{Endian, prelude::*};
10
11const CHAINED_ITEM_DEFAULT_OFFSET_PAD: u32 = 4;
12
13pub const CHAINED_ITEM_PREFIX_SIZE: usize = std::mem::size_of::<NextEntryOffsetType>();
19
20type NextEntryOffsetType = u32;
21
22#[derive(Debug, PartialEq, Eq)]
35pub struct ChainedItemList<T, const OFFSET_PAD: u32 = CHAINED_ITEM_DEFAULT_OFFSET_PAD> {
36 values: Vec<T>,
37}
38
39impl<T, const OFFSET_PAD: u32> ChainedItemList<T, OFFSET_PAD> {
40 #[inline]
42 pub fn iter(&self) -> impl Iterator<Item = &T> {
43 self.values.iter()
44 }
45
46 #[inline]
48 pub fn is_empty(&self) -> bool {
49 self.values.is_empty()
50 }
51
52 #[inline]
54 pub fn len(&self) -> usize {
55 self.values.len()
56 }
57}
58
59impl<T, const OFFSET_PAD: u32> BinWrite for ChainedItemList<T, OFFSET_PAD>
60where
61 T: BinWrite,
62 for<'b> <T as BinWrite>::Args<'b>: Default,
63{
64 type Args<'a> = ();
65
66 #[allow(clippy::ptr_arg)] fn write_options<W: Write + Seek>(
68 &self,
69 writer: &mut W,
70 endian: Endian,
71 _args: Self::Args<'_>,
72 ) -> BinResult<()> {
73 for (i, item) in self.values.iter().enumerate() {
74 let position_before = writer.stream_position()?;
75
76 let next_entry_offset_pos = writer.stream_position()?;
78 NextEntryOffsetType::write_options(&0u32, writer, endian, ())?;
79
80 item.write_options(writer, endian, Default::default())?;
82
83 if i == self.values.len() - 1 {
85 break;
86 }
87
88 let position_after_item = writer.stream_position()?;
89 let padding_needed =
90 (OFFSET_PAD as u64 - (position_after_item % OFFSET_PAD as u64)) % OFFSET_PAD as u64;
91 writer.seek(SeekFrom::Current(padding_needed as i64))?;
92 debug_assert!(
93 writer.stream_position()? % OFFSET_PAD as u64 == 0,
94 "ChainedItemList item not aligned to OFFSET_PAD {} after padding",
95 OFFSET_PAD
96 );
97
98 let position_after = writer.stream_position()?;
100 let next_entry_offset = if i == self.values.len() - 1 {
101 0u32
102 } else {
103 (position_after - position_before) as u32
104 };
105
106 writer.seek(SeekFrom::Start(next_entry_offset_pos))?;
108 NextEntryOffsetType::write_options(&next_entry_offset, writer, endian, ())?;
109
110 writer.seek(SeekFrom::Start(position_after))?;
111 }
112 Ok(())
113 }
114}
115
116impl<T, const OFFSET_PAD: u32> BinRead for ChainedItemList<T, OFFSET_PAD>
117where
118 T: BinRead,
119 for<'b> <T as BinRead>::Args<'b>: Default,
120{
121 type Args<'a> = ();
122
123 fn read_options<R: Read + Seek>(
124 reader: &mut R,
125 endian: Endian,
126 _args: Self::Args<'_>,
127 ) -> BinResult<Self> {
128 let stream_end = {
129 let current = reader.stream_position()?;
130 let end = reader.seek(SeekFrom::End(0))?;
132 reader.seek(SeekFrom::Start(current))?;
134 end
135 };
136 if reader.stream_position()? == stream_end {
137 return Ok(Self { values: Vec::new() });
139 }
140
141 let mut values = Vec::new();
142 loop {
143 let position_before = reader.stream_position()?;
144
145 if position_before % OFFSET_PAD as u64 != 0 {
146 return Err(binrw::Error::AssertFail {
147 pos: position_before,
148 message: format!(
149 "ChainedItemList item not aligned to OFFSET_PAD {}",
150 OFFSET_PAD
151 ),
152 });
153 }
154
155 let next_item_offset = NextEntryOffsetType::read_options(reader, endian, ())?;
156
157 let item: T = T::read_options(reader, endian, Default::default())?;
158
159 values.push(item);
160
161 if next_item_offset == 0 {
162 break;
163 }
164 reader.seek(SeekFrom::Start(position_before + next_item_offset as u64))?;
165 }
166 Ok(Self { values })
167 }
168}
169
170impl<T, const OFFSET_PAD: u32> From<ChainedItemList<T, OFFSET_PAD>> for Vec<T> {
171 fn from(value: ChainedItemList<T, OFFSET_PAD>) -> Self {
172 value.values
173 }
174}
175
176impl<T, const OFFSET_PAD: u32> From<Vec<T>> for ChainedItemList<T, OFFSET_PAD> {
177 fn from(vec: Vec<T>) -> Self {
178 Self { values: vec }
179 }
180}
181
182impl<T, const OFFSET_PAD: u32> FromIterator<T> for ChainedItemList<T, OFFSET_PAD> {
183 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
184 let values = iter.into_iter().collect();
185 Self { values }
186 }
187}
188
189impl<T, const OFFSET_PAD: u32> std::ops::Deref for ChainedItemList<T, OFFSET_PAD> {
190 type Target = [T];
191
192 fn deref(&self) -> &Self::Target {
193 &self.values
194 }
195}
196
197impl<T, const OFFSET_PAD: u32> std::ops::DerefMut for ChainedItemList<T, OFFSET_PAD> {
198 fn deref_mut(&mut self) -> &mut Self::Target {
199 &mut self.values
200 }
201}
202
203impl<T, const OFFSET_PAD: u32> Default for ChainedItemList<T, OFFSET_PAD> {
204 fn default() -> Self {
205 Self { values: Vec::new() }
206 }
207}