prs_rs/impls/decomp/
decompress.rs

1use super::common::{read_byte, read_two_le, retrieve_control_bit};
2
3pub(crate) unsafe fn prs_decompress(mut source: *const u8, mut dest: *mut u8) -> usize {
4    let mut control_byte = read_byte(&mut source);
5    let mut current_bit_position = 0;
6    let mut file_size = 0;
7
8    loop {
9        // Test for Direct Byte (Opcode 1)
10        if retrieve_control_bit(&mut control_byte, &mut current_bit_position, &mut source) == 1 {
11            *dest = read_byte(&mut source) as u8;
12            dest = dest.add(1);
13            file_size += 1;
14            continue;
15        }
16
17        // Opcode 1 failed, now testing for Opcode 0X
18        if retrieve_control_bit(&mut control_byte, &mut current_bit_position, &mut source) == 1 {
19            // Test for Opcode 01
20            // Append size of long copy, break if it's end of file.
21            if decode_long_copy(&mut source, &mut dest, &mut file_size) {
22                break;
23            }
24        } else {
25            // Do Opcode 00
26            decode_short_copy(
27                &mut control_byte,
28                &mut current_bit_position,
29                &mut source,
30                &mut dest,
31                &mut file_size,
32            );
33        }
34    }
35
36    file_size
37}
38
39#[inline]
40unsafe fn decode_long_copy(
41    source: &mut *const u8,
42    dest: &mut *mut u8,
43    file_size: &mut usize,
44) -> bool {
45    // Opcode 01, length 2 - 256
46    let ofs_bytes = read_two_le(source) as isize;
47    if ofs_bytes == 0 {
48        return true;
49    }
50
51    // Obtain the offset. (negative i32, truncated to u16)
52    // We lost our negative sign when we originally wrote the offset, doing -0x2000 will restore it.
53    let offset = (ofs_bytes >> 3) | -0x2000;
54
55    // Perf:
56    // Calculate offset first, because length is more 'local', it's used by the
57    // loop, while ofs is only used once.
58    let length = ofs_bytes as usize & 0b111;
59    let length = if length == 0 {
60        read_byte(source) + 1 // length: 2 - 256
61    } else {
62        length + 2 // length: 2 - 9
63    };
64
65    let dest_local = *dest; // hoist the variable for perf
66    let src_addr = dest_local.wrapping_add(offset as usize);
67    for i in 0..length {
68        *dest_local.add(i) = *src_addr.add(i);
69    }
70
71    *dest = dest_local.add(length);
72    *file_size += length;
73    false
74}
75
76#[inline]
77unsafe fn decode_short_copy(
78    control_byte: &mut usize,
79    current_bit_position: &mut usize,
80    source: &mut *const u8,
81    dest: &mut *mut u8,
82    file_size: &mut usize,
83) {
84    // Opcode 00, length 2-5
85    let mut length = retrieve_control_bit(control_byte, current_bit_position, source) << 1;
86    length |= retrieve_control_bit(control_byte, current_bit_position, source);
87    length += 2;
88
89    // Obtain the offset. (negative i32, truncated to byte)
90    // We lost our sign when we originally wrote the offset, doing -0x100 will restore it.
91    let offset = read_byte(source) as isize | -0x100; // negative
92
93    // Copy from source to dest
94    // LLVM is magical, it just optimises this knowing max length is 5.
95    // I have no idea how, given complexity of everything, but it does.
96    // This ends up being very nice unrolled code copy.
97    let dest_local = *dest; // hoist the variable for perf
98    let src_addr = dest_local.wrapping_add(offset as usize);
99    for i in 0..length {
100        *dest_local.add(i) = *src_addr.add(i);
101    }
102
103    *dest = dest_local.add(length);
104    *file_size += length;
105}