binrw icon indicating copy to clipboard operation
binrw copied to clipboard

`FilePtr` and `if` concerns

Open e820 opened this issue 1 year ago • 2 comments

I've been toying with binrw for about a day now to get a feel for it and I'm a bit troubled by the current FilePtr state so I thought I'd ask for some work arounds.

First of all, the file format I'm parsing has some file offsets that are 0 when the pointee is not present. How would you handle this in binrw? As far as I can tell the if conditional guard doesn't support this as it doesn't see the current field's value - only the values of previous fields.

Said file offset points to an extension of the file header. Both the header and the header extension store length information that have to be added together to reach the start of the actual file data. Thus I decided to go with a custom parser as that seemed like the only option:

#[br(parse_with = parse_files)]
#[br(args(header_len, ext_header.value.as_ref()))]
files: Vec<File>,

Notice how I'm passing both the header length, aswell as the header extension to calculate the start of the file data in the parser:

fn parse_files<R>(
    r: &mut R,
    _opt: &ReadOptions,
    (header_len, header_ext): (u16, Option<&VolumeHeaderExt>),
) -> BinResult<Vec<File>>
where
    R: Read + Seek,
{
    // Skip over the entire volume header
    let total_len = header_len as u32 + header_ext.map(|ext| ext.len).unwrap_or(0);

This does not work though as the FilePtr's value will always be None when the file parser is invoked as after_parse has not been called yet.

Any suggestions on how to work around these two problems are greatly appreciated.

e820 avatar Jul 14 '22 08:07 e820

There are workarounds you can pull by making use of the seek_before attribute to read the offset field twice, though it's extremely janky.

Something that might be slightly more elegant is making use of NonZeroU32 and similar types, to cause a parse error. Then you can make use of the #[br(try)] attribute and Option. This doesn't currently work, but we could make it work by implementing IntoSeekFrom on those types in binrw.

Long term, I want the option to decouple reading the offset from reading the contents at that offset... though it just occurred to me that you can make it work with the current version of the library by creating a zero sized type that implements BinRead and returns 0 from IntoSeekFrom. Then you can use the #[br(offset = ...)] attribute to use the offset that you read earlier along with if.

I'm going to come back in a bit and provide a code example for that last workaround.

kitlith avatar Jul 14 '22 19:07 kitlith

Example code for the last workaround: (playground)

#[derive(Debug, BinRead, Clone, Copy)]
struct Dummy;

impl IntoSeekFrom for Dummy {
    fn into_seek_from(self) -> SeekFrom {
        SeekFrom::Start(0)
    }
}

type Placement<T> = FilePtr<Dummy, T>;

#[derive(Debug, BinRead)]
struct Test {
    offset: u8,
    #[br(if(offset != 0), offset = offset as u64)]
    data: Option<Placement<u8>>,
}

If you need a relative seek instead of an absolute seek, then you can change the IntoSeekFrom implementation accordingly.

kitlith avatar Jul 14 '22 20:07 kitlith