thindx/headers/d3dcommon.h/interfaces/
blob.rs

1use crate::*;
2
3use winapi::um::d3dcommon::ID3DBlob;
4
5use std::borrow::{Borrow, Cow};
6use std::fmt::{self, Debug, Display, Formatter};
7use std::ops::{Deref, Index};
8use std::slice::SliceIndex;
9use std::str::Utf8Error;
10
11
12
13/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ff728743(v=vs.85))\]
14/// ID3DBlob
15///
16/// This interface is used to return arbitrary-length data.
17///
18/// ### ⚠️ Safety ⚠️
19/// This assumes `ReadOnlyBlob`s are read-only once created.
20/// While enforced by the safe interface here, raw `unsafe` winapi can totally let you write to the buffer.
21/// This is your one warning!
22#[derive(Clone)]
23#[repr(transparent)]
24pub struct ReadOnlyBlob(pub(crate) mcom::Rc<ID3DBlob>);
25
26convert!(unsafe ReadOnlyBlob => Unknown, winapi::um::d3dcommon::ID3DBlob);
27
28impl ReadOnlyBlob {
29    /// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ff728745(v=vs.85))\]
30    /// ID3DBlob::GetBufferSize
31    ///
32    /// Gets the size of the buffer.
33    pub fn get_buffer_size(&self) -> usize {
34        unsafe { self.0.GetBufferSize() }
35    }
36
37    /// [ID3DBlob::GetBufferPointer](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ff728744(v=vs.85)) +
38    /// [ID3DBlob::GetBufferSize](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ff728745(v=vs.85))
39    ///
40    /// Gets the data of the buffer as a readonly slice.
41    pub fn get_buffer(&self) -> &[u8] {
42        let size = self.get_buffer_size();
43        let ptr = unsafe { self.0.GetBufferPointer() } as *const u8;
44        unsafe { std::slice::from_raw_parts(ptr, size) }
45    }
46
47    /// Gets the data of the buffer as a readonly slice of bytes.
48    pub fn as_bytes(&self) -> &[u8] {
49        self.get_buffer()
50    }
51
52
53    /// Gets the data of the buffer as an iterator over the bytes.
54    pub fn bytes(&self) -> impl Iterator<Item = u8> + '_ {
55        self.get_buffer().iter().copied()
56    }
57}
58
59impl AsRef <[u8]> for ReadOnlyBlob { fn as_ref(&self) -> &[u8] { self.get_buffer() } }
60impl Borrow<[u8]> for ReadOnlyBlob { fn borrow(&self) -> &[u8] { self.get_buffer() } }
61impl Debug        for ReadOnlyBlob { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!(fmt, "ReadOnlyBlob({} bytes)", self.get_buffer_size()) } }
62impl<I> Index<I>  for ReadOnlyBlob where I: SliceIndex<[u8]> { fn index(&self, index: I) -> &Self::Output { self.get_buffer().index(index) } type Output = I::Output; }
63
64// can't impl Deref - conflicting impl with COM nonsense
65
66// Index?
67// IntoIterator?
68// [Partial]Ord/[Partial]Eq? (nah - ambiguous between bytes vs com instances)
69// Hash? (nah - might want to allow Borrow<[u32]>?)
70
71// From?  Would require manual ID3DBlob impls in case d3dcompiler.dll isn't available/loaded?
72
73#[test] fn layout() {
74    use std::mem::*;
75    assert_eq!(align_of::<Option<ReadOnlyBlob>>(), align_of::<*mut ID3DBlob>());
76    assert_eq!(size_of::< Option<ReadOnlyBlob>>(), size_of::< *mut ID3DBlob>());
77}
78
79
80
81/// [ReadOnlyBlob] wrapper for `\0`-terminated UTF8ish data
82#[derive(Clone, Default)]
83pub struct TextBlob(Option<ReadOnlyBlob>);
84
85impl TextBlob {
86    /// Wraps a `\0`-terminated [ReadOnlyBlob] or [Option]\<[ReadOnlyBlob]\> in a more [std::ffi::CString]-esque interface.
87    //#allow_missing_argument_docs
88    pub fn new(value: impl Into<Self>) -> Self { value.into() }
89
90    /// Check if the string is empty ("\0" counts as empty)
91    pub fn is_empty(&self) -> bool { self.0.as_ref().map_or(true, |blob| {
92        let b = blob.get_buffer();
93        b.is_empty() || b == [0]
94    })}
95
96    /// Treat the blob as a UTF8 string, converting lossily if necessary.
97    pub fn to_utf8_lossy(&self) -> Cow<str> { String::from_utf8_lossy(self.as_bytes()) }
98
99    /// Treat the blob as a UTF8 string, returning a [std::str::Utf8Error] if it's not valid UTF8.
100    pub fn to_utf8(&self) -> Result<&str, Utf8Error> { std::str::from_utf8(self.as_bytes()) }
101
102    // TODO: parsing diagnostics iterator?
103}
104
105impl Debug   for TextBlob { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!(fmt, "TextBlob({:?})", self.to_utf8_lossy()) } }
106impl Display for TextBlob { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { Display::fmt(&self.to_utf8_lossy(), fmt) } }
107
108impl From<Option<ReadOnlyBlob>> for TextBlob { fn from(value: Option<ReadOnlyBlob>) -> Self { Self(     value ) } }
109impl From<       ReadOnlyBlob > for TextBlob { fn from(value:        ReadOnlyBlob ) -> Self { Self(Some(value)) } }
110
111impl TextBlob {
112    fn as_bytes(&self) -> &[u8] {
113        self.0.as_ref().map_or(&[], |blob| {
114            let b = blob.get_buffer();
115            if b.last() == Some(&0) {
116                &b[..b.len()-1]
117            } else {
118                b
119            }
120        })
121    }
122}
123
124
125
126/// [ReadOnlyBlob] wrapper for DXBC or DXIL bytecode.
127#[derive(Clone)]
128#[repr(transparent)]
129pub struct CodeBlob(ReadOnlyBlob);
130
131impl CodeBlob {
132    /// Create a new [CodeBlob] from `value`, assuming it contains valid DXBC or DXIL bytecode.
133    ///
134    /// ### ⚠️ Safety ⚠️
135    /// By calling this fn, you assert that `value` contains valid DXBC or DXIL bytecode.
136    /// While it doesn't result in instant undefined behavior to violate this constraint, many "safe" fns rely on this constraint, and will exhibit undefined behavior if passed an invalid blob.
137    ///
138    /// Parsing and deserialization APIs are prone to undefined behavior and CVEs, and executable bytecode formats are no exception.
139    /// Possible consumers of this bytecode include d3dcompiler, d3d9, d3d11, d3d12, GPU drivers, various third party shader translators, etc.
140    /// If there's not a single path to undefined behavior between all of those, I'll eat my hat!
141    //#allow_missing_argument_docs
142    pub(crate) unsafe fn from_unchecked(value: ReadOnlyBlob) -> Self { Self(value) }
143
144    /// Get the length of the bytecode, in bytes.
145    #[allow(clippy::len_without_is_empty)] // An empty buffer is **not** valid DXBC or DXIL bytecode, and would thus be an invalid construction of this type!
146    pub fn len(&self)      -> usize { self.0.get_buffer_size() }
147
148    /// Interpret the bytecode as a byte array.
149    pub fn as_bytes(&self) -> &[u8] { self.0.get_buffer() }
150
151    /// Interpret the bytecode as a [d3d::Bytecode].
152    pub fn as_bytecode(&self) -> &d3d::Bytecode { unsafe { d3d::Bytecode::from_unchecked(self.as_bytes()) } }
153}
154
155impl AsRef <[u8]> for CodeBlob { fn as_ref(&self) -> &[u8] { self.as_bytes() } }
156impl Borrow<[u8]> for CodeBlob { fn borrow(&self) -> &[u8] { self.as_bytes() } }
157impl AsRef <d3d::Bytecode> for CodeBlob { fn as_ref(&self) -> &d3d::Bytecode { self.as_bytecode() } }
158impl Borrow<d3d::Bytecode> for CodeBlob { fn borrow(&self) -> &d3d::Bytecode { self.as_bytecode() } }
159impl Debug for CodeBlob { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!(fmt, "CodeBlob({} bytes)", self.len()) } }
160impl Deref for CodeBlob { fn deref(&self) -> &d3d::Bytecode { self.as_bytecode() } type Target = d3d::Bytecode; }
161
162// DO NOT IMPLEMENT:
163// From<Option<ReadOnlyBlob>> for TextBlob - "safe" bypass of bytecode validation
164// From<       ReadOnlyBlob > for TextBlob - "safe" bypass of bytecode validation
165// From<&[u8]>                for TextBlob - "safe" bypass of bytecode validation
166
167
168
169
170/// [ReadOnlyBlob] wrapper for other binary data
171#[derive(Clone)]
172#[repr(transparent)]
173pub struct BytesBlob(Option<ReadOnlyBlob>);
174
175impl BytesBlob {
176    /// Wraps a [ReadOnlyBlob] or [Option]\<[ReadOnlyBlob]\> in a more &\[[u8]\]-esque interface.
177    //#allow_missing_argument_docs
178    pub fn new(value: impl Into<Self>) -> Self { value.into() }
179
180    /// Get the length of the binary data, in bytes.
181    pub fn len(&self)      -> usize   { self.0.as_ref().map_or(0,    |blob| blob.get_buffer_size()) }
182
183    /// Check if there is any data.
184    pub fn is_empty(&self) -> bool    { self.0.as_ref().map_or(true, |blob| blob.get_buffer_size() == 0) }
185
186    /// Interpret the data as a slice of bytes.
187    pub fn as_bytes(&self)   -> &[u8] { self.0.as_ref().map_or(&[],  |blob| blob.get_buffer()) }
188}
189
190impl AsRef <[u8]> for BytesBlob { fn as_ref(&self) -> &[u8] { self.as_bytes() } }
191impl Borrow<[u8]> for BytesBlob { fn borrow(&self) -> &[u8] { self.as_bytes() } }
192impl Debug for BytesBlob { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!(fmt, "BytesBlob({} bytes)", self.len()) } }
193impl Deref for BytesBlob { fn deref(&self) -> &[u8] { self.as_bytes() } type Target = [u8]; }
194impl From<Option<ReadOnlyBlob>> for BytesBlob { fn from(value: Option<ReadOnlyBlob>) -> Self { Self(     value ) } }
195impl From<       ReadOnlyBlob > for BytesBlob { fn from(value:        ReadOnlyBlob ) -> Self { Self(Some(value)) } }