1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
use crate::*;
use crate::d3d::*;
use std::convert::TryInto;
use std::ptr::*;
/// <h1 id="archive" class="section-header"><a href="#archive">Manipulate Bytecode Archives</a></h1>
impl Compiler {
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompressshaders)\]
/// D3DCompressShaders
///
/// Compresses a set of shaders into a more compact form.
///
/// ### Arguments
/// * `shaders` - The compiled shader(s) to compress.
/// * `flags` - [CompressShader] flags controlling compression.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_42.dll` and earlier
///
/// ### Example
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
/// let plain_txt = std::fs::read(r"test\data\plain.txt").unwrap();
/// let tocompress = [
/// ShaderData::from(&basic_hlsl[..]),
/// ShaderData::from(&plain_txt[..]),
/// ];
/// println!("tocompress: [{} bytes, {} bytes]", basic_hlsl.len(), plain_txt.len());
///
/// let compress = d3dc.compress_shaders(&tocompress, CompressShader::default()).unwrap();
/// println!("compressed: {} bytes", compress.len());
/// ```
///
/// ### Output
/// ```text
/// to_compress: [493 bytes, 58 bytes]
/// compressed: 432 bytes
/// ```
///
/// ### Remarks
/// * You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
/// * This was introduced by d3dcompiler_43.dll, and is unavailable in earlier versions.
pub fn compress_shaders(
&self,
shaders: &[ShaderData],
flags: impl Into<CompressShader>,
) -> Result<BytesBlob, MethodError> {
// Early outs
let f = self.D3DCompressShaders.ok_or(MethodError("D3DCompressShaders", THINERR::MISSING_DLL_EXPORT))?;
let num_shaders = shaders.len().try_into().map_err(|_| MethodError::new("D3DCompressShaders", THINERR::MISSING_DLL_EXPORT))?;
// SAFETY: ⚠️ This sketchy mut cast *should* be sane (famous last words.)
// We're only casting away the immutability of the D3D_SHADER_DATA elements, not the bytecode those elements point to.
// The array is far too small to reuse as an in-place compression buffer or anything like that.
// Additionally, in manual testing, I see no mutation of the underlying array.
// Finally, `pShaderData` is marked `_In_reads_(uNumShaders)` - if this was actually written to, it should be `_Inout_count_` instead.
let shader_data = shaders.as_ptr() as *mut _;
let flags = flags.into().into();
// SAFETY: ❌ needs fuzz testing for alloc overflow bugs
// * `f` ✔️ should be valid/sound like all `self.*`
// * `num_shaders` ⚠️ could truncate within `D3DCompressShaders` (harmless?)
// * `shader_data` ❌ could overflow an alloc within `D3DCompressShaders`
// * `shader_data` ✔️ may contain arbitrary non-shader data
// * `flags` ⚠️ could be invalid
// * `compressed_data` ✔️ is a trivial out-param
let mut compressed_data = null_mut();
let hr = unsafe { f(num_shaders, shader_data, flags, &mut compressed_data) };
MethodError::check("D3DCompressShaders", hr)?;
// SAFETY: ✔️
// * `compressed_data` ✔️ is either null (from_raw panics) or a valid, owned, non-dangling ID3DBlob (ownership transfers)
// * `ReadOnlyBlob` ✔️ imposes no restrictions on `compressed_data`'s contents.
Ok(BytesBlob::new(unsafe { ReadOnlyBlob::from_raw(compressed_data) }))
}
// D3DDecompressShaders behavior:
// * if `ppShaders` is null, E_FAIL, even if you're requesting 0 shaders
// * if `indices` is specified:
// * `start_index` is ignored
// * `indices[0..num_shaders]` is read for archive indexes to read
// * if `indices` has duplicates, those are null
// * if `indices` is null:
// * treated as if indicies == (start_index .. start_index + num_shaders).collect()
//
// https://github.com/MaulingMonkey/thindx/issues/5
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3ddecompressshaders)\]
/// D3DDecompressShaders
///
/// Get the number of compressed shaders in the `src_data` archive.
///
/// ### Arguments
/// * `src_data` - The compressed shaders.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_42.dll` and earlier
///
/// ### Example
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// # let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
/// # let plain_txt = std::fs::read(r"test\data\plain.txt").unwrap();
/// # let tocompress = [
/// # ShaderData::from(&basic_hlsl[..]),
/// # ShaderData::from(&plain_txt[..]),
/// # ];
/// #
/// # let compress = d3dc.compress_shaders(&tocompress, CompressShader::default()).unwrap();
/// assert_eq!(2, d3dc.decompress_shaders_count(&compress).unwrap());
/// ```
///
/// ### Remarks
/// * You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
/// * This was introduced by d3dcompiler_43.dll, and is unavailable in earlier versions.
pub fn decompress_shaders_count(&self, src_data: &[u8]) -> Result<u32, MethodError> {
let f = self.D3DDecompressShaders.ok_or(MethodError("D3DDecompressShaders", THINERR::MISSING_DLL_EXPORT))?;
let mut shader = null_mut(); // D3DDecompressShaders will fail with E_FAIL if ppShaders is null, even if it doesn't use it
let mut total_shaders = 0;
// SAFETY: ❌ needs fuzz testing for alloc overflow and corruption bugs
// * `f` ✔️ should be valid/sound like all `self.*`
// * `src_data` ⚠️ probably won't overflow an alloc within `D3DDecompressShaders` with these params?
// * `src_data` ❌ could contain corrupt decompression data
// * `shader` ✔️ is unused except for a null ptr check
// * `total_shaders` ⚠️ could be truncated at the 4 GB mark
let hr = unsafe { f(src_data.as_ptr().cast(), src_data.len(), 0, 0, null_mut(), 0, &mut shader, &mut total_shaders) };
debug_assert!(shader.is_null(), "BUG: shader shouldn't have been touched!?!?");
MethodError::check("D3DDecompressShaders", hr)?;
Ok(total_shaders)
}
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3ddecompressshaders)\]
/// D3DDecompressShaders
///
/// Decompresses one or more shaders from a compressed set.
///
/// ### Arguments
/// * `src_data` - The compressed shaders to decompress.
/// * `flags` - Reserved (pass [None]).
/// * `start_index` - The first shader to read, by archive index order.
/// * `out_shaders` - An output buffer of shaders to read. The size of the buffer dictates how many shaders will be read.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_42.dll` and earlier
///
/// ### Example
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// # let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
/// # let plain_txt = std::fs::read(r"test\data\plain.txt").unwrap();
/// # let tocompress = [
/// # ShaderData::from(&basic_hlsl[..]),
/// # ShaderData::from(&plain_txt[..]),
/// # ];
/// #
/// # let compress = d3dc.compress_shaders(&tocompress, CompressShader::default()).unwrap();
/// let mut decompressed = [None, None, None];
/// let decompressed2 = d3dc.decompress_shaders_inplace(
/// &compress, None, 0, &mut decompressed[..]
/// ).unwrap();
///
/// assert_eq!(2, decompressed2.len());
/// assert_eq!(basic_hlsl, decompressed2[0].as_ref().unwrap().get_buffer());
/// assert_eq!(plain_txt, decompressed2[1].as_ref().unwrap().get_buffer());
///
/// assert_eq!(&basic_hlsl[..], decompressed[0].as_ref().unwrap().get_buffer());
/// assert_eq!(&plain_txt[..], decompressed[1].as_ref().unwrap().get_buffer());
/// assert!(decompressed[2].is_none());
/// ```
///
/// ### Remarks
/// * You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
/// * This was introduced by d3dcompiler_43.dll, and is unavailable in earlier versions.
pub fn decompress_shaders_inplace<'s>(
&self,
src_data: &[u8],
flags: impl Into<Option<std::convert::Infallible>>,
start_index: u32,
out_shaders: &'s mut [Option<ReadOnlyBlob>],
) -> Result<&'s [Option<ReadOnlyBlob>], MethodError> {
let f = self.D3DDecompressShaders.ok_or(MethodError("D3DDecompressShaders", THINERR::MISSING_DLL_EXPORT))?;
let n : u32 = out_shaders.len().try_into().map_err(|_| MethodError::new("D3DDecompressShaders", THINERR::SLICE_TOO_LARGE))?;
let _ = flags;
for shader in out_shaders.iter_mut() { *shader = None; } // D3DCompressShaders will overwrite
let mut total_shaders = 0;
// SAFETY: ❌ needs fuzz testing for alloc overflow and corruption bugs
// * `f` ✔️ should be valid/sound like all `self.*`
// * `src_data` ❌ could overflow an alloc within `D3DDecompressShaders`
// * `src_data` ❌ could contain corrupt decompression data
// * `n` ⚠️ "shouldn't" (famous last words) cause cause alloc overflow issues inside `D3DDecompressShaders` ?
// * `flags` ✔️ are reserved / 0
// * `start_index` ❌ could be out-of-bounds
// * `out_shaders` ✔️ should be ABI compatible thanks to the #[repr(transparent)] conversion chain: Option<ReadOnlyBlob> -> Option<mcom::Rc<ID3DBlob>> -> Option<NonNull<ID3DBlob>> -> `*mut ID3DBlob`
// * `out_shaders` ✔️ should not leak (explicitly `None`ed out earlier)
// * `out_shaders` ✔️ should contain sufficient elements to avoid overflow (`n` inferred from `.len`)
let hr = unsafe { f(src_data.as_ptr().cast(), src_data.len(), n, start_index, null_mut(), 0, out_shaders.as_mut_ptr().cast(), &mut total_shaders) };
MethodError::check("D3DDecompressShaders", hr)?;
let read = n.min(total_shaders) as usize;
Ok(&out_shaders[..read])
}
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3ddecompressshaders)\]
/// D3DDecompressShaders
///
/// Decompresses one or more shaders from a compressed set.
///
/// ### Arguments
/// * `src_data` - The compressed shaders to decompress.
/// * `flags` - Reserved (pass [None]).
/// * `_range` - The archive index range to read (pass `..` - other options may be supported in the future.)
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_42.dll` and earlier
///
/// ### Example
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// # let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
/// # let plain_txt = std::fs::read(r"test\data\plain.txt").unwrap();
/// # let tocompress = [
/// # ShaderData::from(&basic_hlsl[..]),
/// # ShaderData::from(&plain_txt[..]),
/// # ];
/// #
/// # let compress = d3dc.compress_shaders(&tocompress, CompressShader::default()).unwrap();
/// let decompressed = d3dc.decompress_shaders(&compress, None, ..).unwrap();
///
/// assert_eq!(2, decompressed.len());
/// assert_eq!(basic_hlsl, decompressed[0].as_ref().unwrap().get_buffer());
/// assert_eq!(plain_txt, decompressed[1].as_ref().unwrap().get_buffer());
/// ```
///
/// ### Remarks
/// * You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
/// * This was introduced by d3dcompiler_43.dll, and is unavailable in earlier versions.
pub fn decompress_shaders(
&self,
src_data: &[u8],
flags: impl Into<Option<std::convert::Infallible>>,
_range: std::ops::RangeFull,
) -> Result<Vec<Option<ReadOnlyBlob>>, MethodError> {
let n = self.decompress_shaders_count(src_data)? as usize;
let mut v = Vec::new();
v.resize_with(n, || None);
let n2 = self.decompress_shaders_inplace(src_data, flags, 0, &mut v[..])?.len();
debug_assert_eq!(n, n2);
Ok(v)
}
}