binread/
file_ptr.rs

1//! A wrapper type for representing a layer of indirection within a file.
2//!
3//! A `FilePtr<P, T>` is composed of two types: a pointer type `P` and a value type `T` where
4//! the pointer type describes an offset to read the value type from. Once read from the file
5//! it can be dereferenced to yield the value it points to.
6//!
7//! ## Example
8//! ```rust
9//! use binread::{prelude::*, io::Cursor, FilePtr};
10//!
11//! #[derive(BinRead)]
12//! struct Test {
13//!     pointer: FilePtr<u32, u8>
14//! }
15//!
16//! let test: Test = Cursor::new(b"\0\0\0\x08\0\0\0\0\xff").read_be().unwrap();
17//! assert_eq!(test.pointer.ptr, 8);
18//! assert_eq!(*test.pointer, 0xFF);
19//! ```
20//!
21//! Example data mapped out:
22//! ```hex
23//!           [pointer]           [value]
24//! 00000000: 0000 0008 0000 0000 ff                   ............
25//! ```
26//!
27//! Use `offset` to change what the pointer is relative to (default: beginning of reader).
28use super::*;
29use core::fmt;
30use core::ops::{Deref, DerefMut};
31
32/// A wrapper type for representing a layer of indirection within a file.
33///
34/// A `FilePtr<P, T>` is composed of two types: a pointer type `P` and a value type `T` where
35/// the pointer type describes and offset to read the value type from. Once read from the file
36/// it can be dereferenced to yeild the value it points to.
37///
38/// ## Example
39/// ```rust
40/// use binread::{prelude::*, io::Cursor, FilePtr};
41///
42/// #[derive(BinRead)]
43/// struct Test {
44///     pointer: FilePtr<u32, u8>
45/// }
46///
47/// let test: Test = Cursor::new(b"\0\0\0\x08\0\0\0\0\xff").read_be().unwrap();
48/// assert_eq!(test.pointer.ptr, 8);
49/// assert_eq!(*test.pointer, 0xFF);
50/// ```
51///
52/// Example data mapped out:
53/// ```hex
54///           [pointer]           [value]
55/// 00000000: 0000 0008 0000 0000 ff                   ............
56/// ```
57///
58/// Use `offset` to change what the pointer is relative to (default: beginning of reader).
59pub struct FilePtr<Ptr: IntoSeekFrom, BR: BinRead> {
60    pub ptr: Ptr,
61    pub value: Option<BR>,
62}
63
64/// Type alias for 8-bit pointers
65pub type FilePtr8<T> = FilePtr<u8, T>;
66/// Type alias for 16-bit pointers
67pub type FilePtr16<T> = FilePtr<u16, T>;
68/// Type alias for 32-bit pointers
69pub type FilePtr32<T> = FilePtr<u32, T>;
70/// Type alias for 64-bit pointers
71pub type FilePtr64<T> = FilePtr<u64, T>;
72/// Type alias for 128-bit pointers
73pub type FilePtr128<T> = FilePtr<u128, T>;
74
75impl<Ptr: BinRead<Args = ()> + IntoSeekFrom, BR: BinRead> BinRead for FilePtr<Ptr, BR> {
76    type Args = BR::Args;
77
78    fn read_options<R: Read + Seek>(
79        reader: &mut R,
80        options: &ReadOptions,
81        _: Self::Args,
82    ) -> BinResult<Self> {
83        #[cfg(feature = "debug_template")]
84        let options = &{
85            let mut options = *options;
86
87            let pos = reader.stream_pos().unwrap();
88            let type_name = &core::any::type_name::<Ptr>();
89            if let Some(name) = options.variable_name {
90                binary_template::write_named(
91                    options.endian,
92                    pos,
93                    type_name,
94                    &format!("ptr_to_{}", name),
95                );
96            } else {
97                binary_template::write(options.endian, pos, type_name);
98            }
99            options.dont_output_to_template = true;
100
101            options
102        };
103
104        Ok(FilePtr {
105            ptr: Ptr::read_options(reader, options, ())?,
106            value: None,
107        })
108    }
109
110    fn after_parse<R>(&mut self, reader: &mut R, ro: &ReadOptions, args: BR::Args) -> BinResult<()>
111    where
112        R: Read + Seek,
113    {
114        let relative_to = ro.offset;
115        let before = reader.stream_pos()?;
116        reader.seek(SeekFrom::Start(relative_to))?;
117        reader.seek(self.ptr.into_seek_from())?;
118
119        let mut inner: BR = BinRead::read_options(reader, ro, args)?;
120
121        inner.after_parse(reader, ro, args)?;
122
123        self.value = Some(inner);
124
125        reader.seek(SeekFrom::Start(before))?;
126        Ok(())
127    }
128}
129
130impl<Ptr: BinRead<Args = ()> + IntoSeekFrom, BR: BinRead> FilePtr<Ptr, BR> {
131    /// Custom parser designed for use with the `parse_with` attribute ([example](crate::attribute#custom-parsers))
132    /// that reads a [`FilePtr`](FilePtr) then immediately dereferences it into an owned value
133    pub fn parse<R: Read + Seek>(
134        reader: &mut R,
135        options: &ReadOptions,
136        args: BR::Args,
137    ) -> BinResult<BR> {
138        let mut ptr: Self = Self::read_options(reader, options, args)?;
139        let saved_pos = reader.stream_pos()?;
140        ptr.after_parse(reader, options, args)?;
141        reader.seek(SeekFrom::Start(saved_pos))?;
142        Ok(ptr.into_inner())
143    }
144
145    /// Consume the pointer and return the inner type
146    ///
147    /// # Panics
148    ///
149    /// Will panic if the file pointer hasn't been properly postprocessed
150    pub fn into_inner(self) -> BR {
151        self.value.unwrap()
152    }
153}
154
155/// Used to allow any convert any type castable to i64 into a [`SeekFrom::Current`](io::SeekFrom::Current)
156pub trait IntoSeekFrom: Copy {
157    fn into_seek_from(self) -> SeekFrom;
158}
159
160macro_rules! impl_into_seek_from {
161    ($($t:ty),*) => {
162        $(
163            impl IntoSeekFrom for $t {
164                fn into_seek_from(self) -> SeekFrom {
165                    SeekFrom::Current(self as i64)
166                }
167            }
168        )*
169    };
170}
171
172impl_into_seek_from!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
173
174/// ## Panics
175/// Will panic if the FilePtr has not been read yet using [`BinRead::after_parse`](BinRead::after_parse)
176impl<Ptr: IntoSeekFrom, BR: BinRead> Deref for FilePtr<Ptr, BR> {
177    type Target = BR;
178
179    fn deref(&self) -> &Self::Target {
180        match self.value.as_ref() {
181            Some(x) => x,
182            None => panic!(
183                "Deref'd FilePtr before reading (make sure to use FilePtr::after_parse first)"
184            ),
185        }
186    }
187}
188
189/// ## Panics
190/// Will panic if the FilePtr has not been read yet using [`BinRead::after_parse`](BinRead::after_parse)
191impl<Ptr: IntoSeekFrom, BR: BinRead> DerefMut for FilePtr<Ptr, BR> {
192    fn deref_mut(&mut self) -> &mut BR {
193        match self.value.as_mut() {
194            Some(x) => x,
195            None => panic!(
196                "Deref'd FilePtr before reading (make sure to use FilePtr::after_parse first)"
197            ),
198        }
199    }
200}
201
202impl<Ptr, BR> fmt::Debug for FilePtr<Ptr, BR>
203where
204    Ptr: BinRead<Args = ()> + IntoSeekFrom,
205    BR: BinRead + fmt::Debug,
206{
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        if let Some(ref value) = self.value {
209            fmt::Debug::fmt(value, f)
210        } else {
211            write!(f, "UnreadPointer")
212        }
213    }
214}
215
216impl<Ptr, BR> PartialEq<FilePtr<Ptr, BR>> for FilePtr<Ptr, BR>
217where
218    Ptr: BinRead<Args = ()> + IntoSeekFrom,
219    BR: BinRead + PartialEq,
220{
221    fn eq(&self, other: &Self) -> bool {
222        self.deref() == other.deref()
223    }
224}