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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
use std::io;
use std::ptr::NonNull;
use vtable_rs::VPtr;
use crate::dlkr::DLAllocator;
use shared::{Subclass, Superclass};
use super::{DLFileOperatorContainer, DLFileSeekDirection, DLIOResult};
#[vtable_rs::vtable]
pub trait DLInputStreamVmt {
// Sets the status of last operation.
fn set_last_error(&mut self, status: DLIOResult);
fn destructor(&mut self, param_2: u32);
/// Returns the status of last operation.
fn get_status(&self) -> DLIOResult;
/// Reads length amount of bytes from the stream into output. Returns -1 if the read failed,
/// returns read amount of bytes otherwise.
/// WARNING: Even though the function takes length as usize, it will cast it to u32 and panic if length exceeds 2GB.
fn read_bytes(&mut self, output: *mut u8, length: usize) -> i32;
/// Indicates if there's bytes left for reading.
fn has_bytes_left(&self) -> bool;
/// Indicates the amount of bytes left in the reader.
fn get_bytes_left(&self) -> usize;
/// Skips count amount of bytes, returns the amount of bytes skipped. Will be less than count if
/// position + count exceeds the streams length.
/// WARNING: Even though the function takes count as usize, it will cast it to u32 and panic if count exceeds 2GB.
fn skip_bytes(&mut self, count: usize) -> usize;
/// Closes the stream causing read functions to stop yielding bytes.
fn close_stream(&mut self);
/// Returns true if the stream is open.
fn stream_open(&self) -> bool;
/// Returns the size of one sector for disk this stream is reading from.
/// Implemented only for DLFileInputStream and calls same method on the underlying MicrosoftDiskFileOperator
fn get_disk_sector_size(&self) -> u32;
/// Calls get_disk_sector_size2 on underlying file operator in DLFileInputStream.
/// Underlying file operator's method implemented only for MicrosoftDiskFileOperator and it just calls to get_disk_sector_size, hence
/// the name.
fn get_disk_sector_size_2(&self) -> u32;
/// Calls query_async_status on underlying file operator in DLFileInputStream
/// On any other stream writes 0 and last read bytes count to args
/// Returns true if operation was successful
fn query_async_status(
&mut self,
bytes_remaining: &mut usize,
bytes_transferred: Option<&mut usize>,
) -> bool;
/// On DLFileInputStream calls read_bytes_async on the underlying file operator.
/// On DLMemoryInputStream calls read_bytes on itself and populates last_read_bytes
/// with the amount of bytes read.
fn read_bytes_async(&mut self, output: *mut u8, length: usize) -> bool;
}
#[vtable_rs::vtable]
pub trait DLSeekableInputStreamVmt: DLInputStreamVmt {
fn seek(&mut self, offset: usize, mode: DLFileSeekDirection) -> bool;
/// Returns the current position in the stream (from the start).
fn current_position(&self) -> usize;
/// Checks if the position has reached the end of the stream.
fn reached_end(&self) -> bool;
}
#[repr(C)]
pub struct DLFileInputStream {
pub vftable: VPtr<dyn DLSeekableInputStreamVmt, Self>,
/// Container that used to operate on the disk.
/// Stores pointer to MicrosoftDiskFileOperator.
pub file_operator_container: NonNull<DLFileOperatorContainer>,
/// Status of the latest operation.
pub status: DLIOResult,
}
impl io::Read for DLFileInputStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(self.vftable.read_bytes)(self, buf.as_mut_ptr(), buf.len())
.try_into()
.map_err(io::Error::other)
}
}
#[repr(C)]
pub struct DLMemoryInputStream {
/// Allocator used for this stream.
pub vftable: VPtr<dyn DLSeekableInputStreamVmt, Self>,
/// Capacity of the stream.
pub capacity: usize,
/// Pointer to the start of the stream data.
pub data: *mut u8,
/// Current position in the stream.
pub current_position: usize,
/// Amount of bytes read last time.
/// Used to emulate the behavior of ReadAsync and return the amount of bytes read.
pub last_read_bytes: u32,
/// Indicates if the stream is open.
pub is_open: bool,
// _pad25: [u8; 3],
pub status: DLIOResult,
// _pad2c: [u8; 4],
}
impl DLMemoryInputStream {
/// Safely moves the cursor to [difference] relative to [base].
fn seek_relative(&mut self, difference: i64, base: usize) -> io::Result<u64> {
if !difference.is_negative() {
Ok(self.current_position as u64)
} else if let Some(new) = base.checked_add_signed(difference.try_into_io()?) {
let new = std::cmp::min(new, self.capacity);
self.current_position = new;
Ok(new as u64)
} else {
Err(io::Error::from(io::ErrorKind::InvalidInput))
}
}
}
impl io::Read for DLMemoryInputStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(self.vftable.read_bytes)(self, buf.as_mut_ptr(), buf.len())
.try_into()
.map_err(io::Error::other)
}
}
impl io::Seek for DLMemoryInputStream {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
if !self.is_open {
return Err(io::Error::from(io::ErrorKind::BrokenPipe));
}
match pos {
io::SeekFrom::Start(pos) => {
let new = std::cmp::min(pos.try_into_io()?, self.capacity);
self.current_position = new;
Ok(new as u64)
}
io::SeekFrom::End(difference) => self.seek_relative(difference, self.capacity),
io::SeekFrom::Current(difference) => {
self.seek_relative(difference, self.current_position)
}
}
}
fn stream_position(&mut self) -> io::Result<u64> {
if !self.is_open {
Err(io::Error::from(io::ErrorKind::BrokenPipe))
} else {
Ok(self.current_position as u64)
}
}
}
trait TryIntoIoExt<T>: Sized {
/// Like try_into, but wraps any errors in [io::Error].
fn try_into_io(self) -> io::Result<T>;
}
impl<T, U> TryIntoIoExt<T> for U
where
U: TryInto<T>,
U::Error: std::error::Error + Send + Sync + 'static,
{
fn try_into_io(self) -> io::Result<T> {
self.try_into().map_err(io::Error::other)
}
}
#[repr(C)]
#[derive(Superclass)]
/// Input stream used as base in all decompress streams and DLBufferedInputStream.
pub struct DLPseudoAsyncInputStream {
pub vftable: VPtr<dyn DLSeekableInputStreamVmt, Self>,
/// Amount of bytes read last time.
/// Used to emulate the behavior of ReadAsync and return the amount of bytes read.
pub last_read_bytes: u32,
// _pad: [u8; 4],
}
impl io::Read for DLPseudoAsyncInputStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(self.vftable.read_bytes)(self, buf.as_mut_ptr(), buf.len())
.try_into()
.map_err(io::Error::other)
}
}
#[repr(C)]
#[derive(Subclass)]
/// Buffered input stream that reads data from the source stream into a buffer.
pub struct DLBufferedInputStream {
pub base: DLPseudoAsyncInputStream,
/// Source file stream to read from.
pub source_stream: Option<NonNull<DLFileInputStream>>,
/// Set to true if the source file stream reached the end of the file.
pub is_eof: bool,
// _pad19: [u8; 7],
/// Buffer used to read data from the source stream.
pub buffer: DLStreamBuffer,
}
#[vtable_rs::vtable]
pub trait DLOutputStreamVmt {
/// Sets the status of last operation.
fn set_last_error(&mut self, status: DLIOResult);
fn destructor(&mut self, param_2: u32);
/// Returns the status of last operation.
fn get_last_error(&self) -> DLIOResult;
/// Writes length amount of bytes from input to the stream. Returns 0 if the write failed,
fn write(&mut self, input: *const u8, length: usize) -> usize;
/// Returns amount of bytes that can be written to the stream.
/// On DLFileOutputStream calls max_non_streamed_bytes on the underlying file operator.
fn get_write_size(&self) -> usize;
/// Closes the stream.
/// On DLFileOutputStream calls flush, truncate and close on the underlying file operator.
/// On DLMemoryOutputStream deallocates underlying stream buffer.
fn close(&mut self);
/// Flushes the stream.
/// Implemented only for DLFileOutputStream, but because it calls for flush on MicrosoftDiskFileOperator
/// and flush is not implemented there, it does nothing.
fn flush(&mut self);
/// Returns true if the stream is open.
fn is_open(&self) -> bool;
/// Returns the size of one sector for disk this stream is writing to.
/// Implemented only for DLFileOutputStream and calls same method on the underlying MicrosoftDiskFileOperator which returns sector size of current disk.
/// Probably used for other platforms.
fn get_async_block_size(&self) -> u32;
/// Returns alignment size for async writes.
/// Underlying MicrosoftDiskFileOperator just calls get_async_block_size and returns the result.
fn get_async_buffer_alignment_size(&self) -> u32;
/// Calls query_async_status on underlying file operator in DLFileOutputStream
/// On MemoryOutputStream writes 0 and last written bytes count to args
/// Returns true if operation was successful
fn query_async_status(
&mut self,
bytes_remaining: &mut usize,
bytes_transferred: Option<&mut usize>,
) -> bool;
/// # Safety
///
/// The caller must ensure that the input is valid and the length is correct.
/// On DLFileOutputStream calls start_async_write on the underlying file operator.
/// On DLMemoryOutputStream calls write_bytes on itself and populates last_bytes_written
/// with the amount of bytes written.
unsafe fn start_async_write(&mut self, input: *const u8, length: usize) -> bool;
}
#[vtable_rs::vtable]
pub trait DLSeekableOutputStreamVmt: DLOutputStreamVmt {
fn seek(&mut self, offset: usize, mode: DLFileSeekDirection) -> bool;
fn set_auto_flush(&mut self, auto_flush: bool);
fn pad_to_align(&mut self, alignment: usize, padding_byte: u8) -> bool;
}
#[repr(C)]
pub struct DLFileOutputStream {
pub vftable: VPtr<dyn DLSeekableOutputStreamVmt, Self>,
/// Container that used to operate on the disk.
/// Stores pointer to MicrosoftDiskFileOperator.
pub file_operator_container: NonNull<DLFileOperatorContainer>,
/// Controls should the stream be flushed and truncated on close.
pub set_eof_on_close: bool,
// _pad11: [u8; 3],
/// Status of the latest operation.
pub status: DLIOResult,
}
impl io::Write for DLFileOutputStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok((self.vftable.write)(self, buf.as_ptr(), buf.len()))
}
fn flush(&mut self) -> io::Result<()> {
(self.vftable.flush)(self);
Ok(())
}
}
#[repr(C)]
pub struct DLStreamBuffer {
/// Allocator used for this stream.
pub allocator: &'static DLAllocator,
/// Pointer to the start of the buffer data.
pub data: *mut u8,
/// Capacity of the buffer.
pub capacity: usize,
/// Controls buffer growth behavior.
pub auto_resize: bool,
// _pad19: [u8; 7],
/// Offset of which the data starts.
/// Can be not zero if the stream is aligned to a certain size.
pub data_offset: usize,
/// Current position in the stream.
pub current_position: usize,
/// Max ever used position in the stream.
pub max_position: usize,
/// Size to grow the buffer by when it runs out of space.
pub growth_factor: usize,
}
#[repr(C)]
pub struct DLMemoryOutputStream {
pub vftable: VPtr<dyn DLSeekableOutputStreamVmt, Self>,
/// Underlying stream buffer.
pub stream_buffer: DLStreamBuffer,
/// Count of bytes written last time.
/// Used to emulate the behavior of WriteAsync and return the amount of bytes written.
pub last_bytes_written: usize,
/// Status of latest operation.
pub status: DLIOResult,
// _pad: [u8; 4],
}
impl io::Write for DLMemoryOutputStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok((self.vftable.write)(self, buf.as_ptr(), buf.len()))
}
fn flush(&mut self) -> io::Result<()> {
(self.vftable.flush)(self);
Ok(())
}
}