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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*!
  Code for dealing with compressed and uncompressed objects in PMW1 EXE files.

  Each object contains code and data, and some basic directions for mapping it into memory.
  It also has associated relocation blocks, to handle relocating data into the memory space of other objects.
  */

use std::convert::TryInto; // To turn slices into arrays...

// It seems appropriate to craft I/O errors for this, instead of the overhead of maintaining my own
// error class...
use std::io::{Result,Error,ErrorKind};

use crate::codec::{encode,decode};
use crate::reloc::Pmw1RelocBlock;
use crate::constants::*;

/// A struct representing an object in a PMW1 executable, and containing its associated relocation
/// blocks.
#[derive(Debug,Clone,PartialEq,Eq,Hash)]
pub struct Pmw1Object {
    virtual_size:u32,
    //actual_size:u32,
    flags:u32,
    relocation_blocks:Vec<Pmw1RelocBlock>,
    uncompressed_size:u32,
    data:Vec<u8>,
}

impl Pmw1Object {
    /// Create a new object - can be used to (re-)build an entire PMW1 EXE from
    /// scratch.
    ///
    /// ## Details:
    /// This function allows you to specify:
    /// * The object's raw `data`, as a slice of bytes.
    /// * The relocation blocks, by passing a mutable iterator over [`Pmw1RelocBlock`]s.
    /// * The object's `virtual_size`, in bytes (i.e. how much memory is mapped at runtime for this
    /// object).
    /// * The object's `flags`, as a 32-bit-string. I don't know if any of these flags is used at all
    /// by PMODE/W.
    pub fn new(data: &[u8], reloc_blocks: &mut dyn Iterator<Item = Pmw1RelocBlock>, virtual_size:u32, flags:u32) -> Self {
        Pmw1Object {
            virtual_size: virtual_size,
            flags: flags,
            relocation_blocks: reloc_blocks.collect(),
            // This is for building an object from scratch, so data should come uncompressed.
            uncompressed_size: data.len() as u32,
            data:data.to_vec(),
        }
    }

    /// Turn an entry from a PMW1 EXE file's object table into an actual object, including its data
    /// and relocation blocks.
    ///
    /// ## Details:
    /// This function facilitates building an object from data scattered at various locations in
    /// an EXE file. As such, you need to pass three different things:
    /// * The `table_entry` itself, as a slice of bytes as they are read from the EXE file's object
    /// table.
    /// * A buffer containing *all* the bytes in the EXE that are designated as relocation data.
    /// The data stored in the table entry indicates where exactly to look in the buffer for this
    /// object's relocation blocks.
    /// * An iterator over the bytes in the data-pages block of the EXE, with its current position
    /// at the start of this object's data.
    ///
    /// ## Requirements:
    /// * The length of `table_entry` must be exactly four times [`PMW1_OBJTABLE_ENTRY_LENGTH`],
    /// otherwise you will get a [`std::io::Error`] of kind
    /// [`InvalidData`](ErrorKind::InvalidData).
    /// * `relocation_buf` and `data_iter` must be able to supply as much data as is specified in
    /// `table_entry` - see the requirements on [`from_tabentry`](Pmw1Object::from_tabentry) for
    /// more details.
    pub fn from_tabentry_byteslice(table_entry: &[u8], relocation_buf: &[u8], data_iter: &mut dyn Iterator<Item = &u8>) -> Result<Self> {
        // Object table entries are made up of dwords.
        if (table_entry.len() % 4) != 0 {
            return Err(Error::new(ErrorKind::InvalidData, "Table entry size not a multiple of 4 (i.e. 32 bits)"));
        }
        let table_entry: Vec<u32> = (0..table_entry.len())
            .step_by(4)
            .map(|i| u32::from_le_bytes([table_entry[i],
                                        table_entry[i+1],
                                        table_entry[i+2],
                                        table_entry[i+3]]))
            .collect();
        Pmw1Object::from_tabentry_slice(&table_entry, relocation_buf, data_iter)
    }

    /// Turn an entry from a PMW1 EXE file's object table into an actual object, including its data
    /// and relocation blocks. This function assumes you have already converted the entry into
    /// 32-bit integers.
    ///
    /// ## Details:
    /// This function facilitates building an object from data scattered at various locations in
    /// an EXE file. As such, you need to pass three different things:
    /// * The `table_entry` itself, as a slice of 32-bit integers as they are encoded in the EXE
    /// file's object table.
    /// * A buffer containing *all* the bytes in the EXE that are designated as relocation data.
    /// The data stored in the table entry indicates where exactly to look in the buffer for this
    /// object's relocation blocks.
    /// * An iterator over the bytes in the data-pages block of the EXE, with its current position
    /// at the start of this object's data.
    ///
    /// ## Requirements:
    /// * The length of `table_entry` must be exactly [`PMW1_OBJTABLE_ENTRY_LENGTH`],
    /// otherwise you will get a [`std::io::Error`] of kind
    /// [`InvalidData`](ErrorKind::InvalidData).
    /// * `relocation_buf` and `data_iter` must be able to supply as much data as is specified in
    /// `table_entry` - see the requirements on [`from_tabentry`](Pmw1Object::from_tabentry) for
    /// more details.
    pub fn from_tabentry_slice(table_entry: &[u32], relocation_buf: &[u8], data_iter: &mut dyn Iterator<Item = &u8>) -> Result<Self> {
        Pmw1Object::from_tabentry(
                match table_entry.try_into() {
                    Ok(arr) => arr,
                    Err(_) => return Err(Error::new(ErrorKind::InvalidData, "Wrong object table entry length")),
                },
                relocation_buf,
                data_iter)
    }

    /// Turn an entry from a PMW1 EXE file's object table into an actual object, including its data
    /// and relocation blocks. This function assumes you have already converted the entry into
    /// 32-bit integers, and that you're in a position to pass them as a fixed-size array.
    ///
    /// ## Details:
    /// This function facilitates building an object from data scattered at various locations in
    /// an EXE file. As such, you need to pass three different things:
    /// * The `table_entry` itself, as an array of [`PMW1_OBJTABLE_ENTRY_LENGTH`] 32-bit integers
    /// as they are encoded in the EXE file's object table.
    /// * A buffer containing *all* the bytes in the EXE that are designated as relocation data.
    /// The data stored in the table entry indicates where exactly to look in the buffer for this
    /// object's relocation blocks.
    /// * An iterator over the bytes in the data-pages block of the EXE, with its current position
    /// at the start of this object's data.
    ///
    /// ## Requirements:
    /// * The `relocation_buf` must be at least `table_entry[3]` bytes long, and contain as much
    /// data beyond that as is needed to construct `table_entry[4]` [`Pmw1RelocBlock`]s.
    /// * `data_iter` must be able to supply at least `table_entry[1]` bytes.
    pub fn from_tabentry(table_entry: &[u32; PMW1_OBJTABLE_ENTRY_LENGTH], relocation_buf: &[u8], data_iter: &mut dyn Iterator<Item = &u8>) -> Result<Self> {
        let mut relocation_buf_iter = match relocation_buf.get((table_entry[3] as usize)..) {
            Some(slice) => slice.iter(),
            None => return Err(Error::new(ErrorKind::UnexpectedEof, "Object claims relocation data are outside the designated region")),
        };

        Ok(Pmw1Object {
            virtual_size:table_entry[0],
            //actual_size:table_entry[1],
            flags:table_entry[2],
            relocation_blocks:
                (0..table_entry[4]).map(|_| {
                    Pmw1RelocBlock::from_byte_iterator(relocation_buf_iter.by_ref())
                }).collect::<Result<_>>()?,
            uncompressed_size:table_entry[5],
            data:data_iter.take(table_entry[1] as usize).map(|&x| x).collect(),
        })
    }

    /// Creates a PMW1 EXE file object table entry (i.e. [`PMW1_OBJTABLE_ENTRY_LENGTH`] 32-bit
    /// integers) to represent this object.
    ///
    /// ## Requirements:
    /// * When using this function to re-construct a PMW1 EXE file, you must know where exactly
    /// this object's relocation blocks will be inserted into the file's relocation data section.
    /// This position needs to be passed as `relocation_pos`.
    pub fn to_tabentry(&self, relocation_pos: u32) -> [u32; PMW1_OBJTABLE_ENTRY_LENGTH] {
        [self.virtual_size,
        self.actual_size() as u32,
        self.flags,
        // This object doesn't know what the relocation buffer of its parent looks like, so this
        // parameter has to be passed to the function:
        relocation_pos, 
        self.relocation_blocks.len() as u32,
        self.uncompressed_size]
    }

    /// Returns this object's `n`th flag, as a [`bool`].
    ///
    /// ## Requirements:
    /// * `n` must be less than 32, since there are only 32 flag bits in total.
    pub fn flag(&self, n:u8) -> Result<bool> {
        if (n >> 5) != 0 {
            Err(Error::new(ErrorKind::InvalidInput, "Trying to read object flag beyond 31"))
        } else {
            Ok((self.flags & (1 << n)) != 0)
        }
    }

    /// Sets this object's `n`th flag according to `val` (1 if `true`, 0 if `false`).
    ///
    /// ## Requirements:
    /// * `n` must be less than 32, since there are only 32 flag bits in total.
    pub fn set_flag(&mut self, n:u8, val:bool) -> Result<()> {
        if (n >> 5) != 0 {
            Err(Error::new(ErrorKind::InvalidInput, "Trying to write object flag beyond 31"))
        } else {
            if val {
                self.flags |= 1 << n;
            } else {
                self.flags &= !(1 << n);
            }
            Ok(())
        }
    }

    /// Consumes this object and returns an uncompressed version (or the same thing if
    /// it's already uncompressed).
    ///
    /// ## Requirements:
    /// * If this object is compressed, the data contained in it must be valid input for the PMW1
    /// decompression algorithm.
    /// * `self.uncompressed_size` must match the actual size produced by the decompression
    /// algorithm. This is a private field, but it can be updated by 
    /// [`update_data_raw`](Pmw1Object::update_data_raw).
    /// * The same must apply to each of this object's relocation blocks.
    ///
    /// If any of these is violated, this will fail with a [`std::io::Error`] of kind
    /// [`InvalidData`](ErrorKind::InvalidData).
    pub fn decompress(self) -> Result<Self> {
        if !self.compressed() {
            println!("Not compressed! Returning the same object...");
            return Ok(self);
        }

        let newobj = Pmw1Object {
            virtual_size:self.virtual_size,
            //actual_size:self.uncompressed_size,
            flags:self.flags,
            relocation_blocks:
                self.relocation_blocks
                .into_iter()
                .map(|x| x.decompress())
                .collect::<Result<Vec<_>>>()?,
            uncompressed_size:self.uncompressed_size,
            data:decode(&self.data, self.uncompressed_size as usize)?,
        };

        if newobj.actual_size() != newobj.uncompressed_size() {
            Err(Error::new(ErrorKind::InvalidData, "Decompressed object size is not as advertised!"))
        } else {
            Ok(newobj)
        }
    }

    /// Consumes this object and returns a compressed version (or the same thing if
    /// it's already compressed).
    ///
    /// ## Requirements:
    /// * The object's data must be at least two bytes long. Otherwise, this will fail
    /// with a [`std::io::Error`] of kind [`InvalidData`](ErrorKind::InvalidData).
    /// * Each of this object's relocation blocks must similarly be compressible.
    pub fn compress(self) -> Result<Self> {
        if self.compressed() {
            println!("Already compressed! Returning the same object...");
            return Ok(self);
        }

        Ok(Pmw1Object {
            virtual_size:self.virtual_size,
            flags:self.flags,
            relocation_blocks:
                self.relocation_blocks
                .into_iter()
                .map(|x| x.compress())
                .collect::<Result<Vec<_>>>()?,
            uncompressed_size:self.uncompressed_size,
            data:encode(&self.data, NUM_PROBES)?,
        })
    }

    /// Returns the current physical size of this object's data, in bytes.
    pub fn actual_size(&self) -> usize {
        self.data.len()
    }

    /// Returns the virtual size in bytes of this object, i.e. how much memory is mapped for it at
    /// runtime.
    pub fn virtual_size(&self) -> usize {
        self.virtual_size as usize
    }

    /// Sets the virtual size in bytes of the object, i.e. how much memory is mapped for it at
    /// runtime.
    ///
    /// I don't know if there are legitimate use-cases for this, but I might as well include it.
    /// *Caveat programmer*.
    pub fn set_virtual_size(&mut self, new_size: u32) {
        self.virtual_size = new_size as u32;
    }

    /// Returns the size in bytes that this object currently believes its data will occupy when
    /// uncompressed.
    ///
    /// I say "believes" because careless use of [`update_data_raw`](Pmw1Object::update_data_raw)
    /// can cause this to go out of sync.
    pub fn uncompressed_size(&self) -> usize {
        self.uncompressed_size as usize
    }

    /// Returns `true` if this object is compressed, `false` otherwise.
    pub fn compressed(&self) -> bool {
        self.actual_size() < self.uncompressed_size()
    }

    /// Returns a slice of bytes containing the data currently stored in this object, regardless of
    /// whether it's compressed or not.
    pub fn data_raw(&self) -> &[u8] {
        &self.data
    }

    /// Returns a [`Vec`] of bytes containing this object's actual code/data, decompressing it if
    /// necessary.
    pub fn data(&self) -> Result<Vec<u8>> {
        let raw_data = self.data_raw();
        if self.compressed() {
            decode(raw_data, self.uncompressed_size as usize)
        } else {
            Ok(raw_data.to_vec())
        }
    }

    /// Applies a closure `f` to the data currently stored in this object, regardless of whether
    /// it's compressed or not.
    ///
    /// ## WARNING:
    /// This function is not `unsafe`, in that it can't cause undefined behaviour in Rust itself,
    /// but it can mess up the state of your object if you're not careful! If you know what you're
    /// doing, calling this function on a compressed object with a carefully-crafted closure may be
    /// faster than [`update_data`](Pmw1Object::update_data), which would decompress, apply the
    /// closure and then recompress the data. However, it is *your* responsibility to make sure
    /// that you know exactly what the uncompressed size of the updated data will be! If
    /// `new_uncompressed_size` is mis-specified here, then you can no longer trust the
    /// [`compressed`](Pmw1Object::compressed) method, or any other method that relies on it, and
    /// your EXE file will likely malfunction when you run it!
    ///
    /// *You have been warned!*
    pub fn update_data_raw<F>(&mut self, f: F, new_uncompressed_size: u32) 
    where F: FnOnce(&[u8]) -> Vec<u8> { 
        self.data = f(self.data_raw());
        self.uncompressed_size = new_uncompressed_size;
    }

    /// Applies a closure `f` to the actual code/data of this object, decompressing and
    /// recompressing it if necessary. The `uncompressed_size` field is updated automatically.
    pub fn update_data<F>(&mut self, f: F) -> Result<()>
        // If I use &str as the error type, the borrow checker gets its knickers in a twist.
        // If I use String, the &str silently gets converted as it bubbles up, and it compiles
        // grand. Go fig...
    where F: FnOnce(&[u8]) -> Vec<u8> { 
        let compressed = self.compressed(); // Store this now since we can't rely on this function when we update self.uncompressed_size!
        let new_data = f(&self.data()?);

        self.uncompressed_size = new_data.len() as u32;
        self.data = if compressed {
            encode(&new_data, NUM_PROBES)?
        } else {
            new_data
        };

        Ok(())
    }

    /// Iterate over this object's [relocation blocks](Pmw1RelocBlock).
    pub fn iter_reloc_blocks(&self) -> std::slice::Iter<Pmw1RelocBlock> {
        self.relocation_blocks.iter()
    }

    /// Iterate mutably over this object's [relocation blocks](Pmw1RelocBlock).
    pub fn iter_reloc_blocks_mut(&mut self) -> std::slice::IterMut<Pmw1RelocBlock> {
        self.relocation_blocks.iter_mut()
    }

    /// Add a new [relocation block](Pmw1RelocBlock) to the object. This makes sure that the block
    /// is compressed or uncompressed as appropriate to the state of the object itself. If
    /// [compression](Pmw1RelocBlock::compress) or [decompression](Pmw1RelocBlock::decompress)
    /// fails, this method returns the error generated by that process.
    pub fn add_reloc_block(&mut self, block: Pmw1RelocBlock) -> Result<()> {
        // Make sure the compression state is correct.
        if self.compressed() {
            self.relocation_blocks.push(block.compress()?);
        } else {
            self.relocation_blocks.push(block.decompress()?);
        }

        Ok(())
    }
}