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
use crate::compression::compression_provider::CompressionProvider;
use anyhow::{anyhow, Result};
use nintendo_lz::decompress_arr;
use std::cmp::min;

fn get_occurrence_length(
    bytes: &[u8],
    new_ptr: usize,
    new_length: usize,
    old_ptr: usize,
    old_length: usize,
) -> (i32, usize) {
    if new_length == 0 || old_length == 0 {
        return (0, 0);
    }

    let mut disp = 0;
    let mut max_length = 0;
    for i in 0..(old_length - 1) {
        let current_old_start = old_ptr + i;
        let mut current_length = 0;
        for j in 0..new_length {
            if bytes[current_old_start + j] != bytes[new_ptr + j] {
                break;
            }
            current_length += 1;
        }
        if current_length > max_length {
            max_length = current_length;
            disp = old_length - i;
            if max_length == new_length {
                break;
            }
        }
    }
    (max_length as i32, disp)
}

pub struct LZ13CompressionProvider;

impl CompressionProvider for LZ13CompressionProvider {
    fn is_compressed_filename(&self, filename: &str) -> bool {
        filename.ends_with(".lz")
    }

    fn compress(&self, bytes: &[u8]) -> Result<Vec<u8>> {
        // First, create the header.
        let mut result: Vec<u8> = Vec::new();
        let length = bytes.len();
        result.reserve(9 + length + ((length - 1) >> 3)); // For performance, reserve space to avoid resizing.
        result.push(0x13);
        result.push(((length & 0xFF) + 1) as u8);
        result.push(((length >> 8) & 0xFF) as u8);
        result.push(((length >> 16) & 0xFF) as u8);
        result.push(0x11);
        result.push((length & 0xFF) as u8);
        result.push(((length >> 8) & 0xFF) as u8);
        result.push(((length >> 16) & 0xFF) as u8);

        // Begin compressing using the DSDecmp algorithm.
        let mut out_buffer: Vec<u8> = Vec::new();
        out_buffer.reserve_exact(8 * 4 + 1);
        out_buffer.push(0);
        let mut buffered_blocks = 0;
        let mut read_bytes = 0;
        while read_bytes < bytes.len() {
            // Dump out buffer contents
            if buffered_blocks == 8 {
                result.append(&mut out_buffer);
                out_buffer.push(0);
                buffered_blocks = 0;
            }

            let old_length = min(read_bytes, 0x1000);
            let (length, disp) = get_occurrence_length(
                bytes,
                read_bytes,
                min(bytes.len() - read_bytes, 0x1000),
                read_bytes - old_length,
                old_length,
            );

            if length < 3 {
                out_buffer.push(bytes[read_bytes]);
                read_bytes += 1;
            } else {
                read_bytes += length as usize;
                out_buffer[0] |= (1 << (7 - buffered_blocks)) as u8;
                if length > 0x110 {
                    out_buffer.push(0x10 | (((length - 0x111) >> 12) & 0x0F) as u8);
                    out_buffer.push((((length - 0x111) >> 4) & 0xFF) as u8);
                    out_buffer.push((((length - 0x111) << 4) & 0xF0) as u8);
                } else if length > 0x10 {
                    out_buffer.push((((length - 0x111) >> 4) & 0x0F) as u8);
                    out_buffer.push((((length - 0x111) << 4) & 0xF0) as u8);
                } else {
                    out_buffer.push((((length - 1) << 4) & 0xF0) as u8);
                }
                let last_index = out_buffer.len() - 1;
                out_buffer[last_index] |= (((disp - 1) >> 8) & 0x0F) as u8;
                out_buffer.push(((disp - 1) & 0xFF) as u8);
            }
            buffered_blocks += 1;
        }
        if buffered_blocks > 0 {
            result.append(&mut out_buffer);
        }
        Ok(result)
    }

    fn decompress(&self, bytes: &[u8]) -> Result<Vec<u8>> {
        if bytes[0] == 0 {
            let mut result: Vec<u8> = Vec::new();
            result.extend_from_slice(&bytes[4..]);
            Ok(result)
        } else {
            let truncated_input = if bytes[0] == 0x13 { &bytes[4..] } else { bytes };

            match decompress_arr(&truncated_input) {
                Ok(decompressed_data) => Ok(decompressed_data),
                Err(_) => Err(anyhow!("Invalid compressed file")),
            }
        }
    }
}