lotus-lib 4.0.2

A library for reading data from Warframe's files stored in the Cache.Windows folder
Documentation
use std::cmp::min_by;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};

use anyhow::Result;
use log::debug;

use crate::compression::lz::decompress_lz;
use crate::compression::oodle::decompress_oodle;

pub fn decompress_post_ensmallening(
    compressed_len: usize,
    decompressed_len: usize,
    cache_reader: &mut File,
) -> Result<Vec<u8>> {
    let mut decompressed_data = vec![0u8; decompressed_len];
    let mut compressed_buffer = vec![0u8; 0x40000];
    let mut decompressed_pos = 0;

    let cache_len = cache_reader.metadata()?.len() as usize;

    while decompressed_pos < decompressed_len {
        let (block_comp_len, block_decomp_len) =
            get_block_lengths(cache_reader)?.unwrap_or((compressed_len, decompressed_len));
        debug!(
            "Decompressing block, compressed_len: {}, decompressed_len: {}",
            block_comp_len, block_decomp_len
        );

        if decompressed_pos + block_decomp_len > decompressed_len {
            return Err(anyhow::anyhow!(
                "Decompressed past the file length, \
                decompressed_pos: {}, decompressed_len: {}, file_len: {}",
                decompressed_pos,
                block_decomp_len,
                decompressed_len
            ));
        }

        let cache_offset = cache_reader.seek(SeekFrom::Current(0))? as usize;
        let remaining_len = cache_len - cache_offset;
        if block_comp_len > min_by(remaining_len, 0x40000, |a, b| a.cmp(b)) {
            return Err(anyhow::anyhow!(
                "Tried to read beyond limits, probably not a compressed file, \
                compressed_len: {}, remaining_len: {}",
                block_comp_len,
                remaining_len
            ));
        }

        let is_oodle = is_oodle_block(cache_reader)?;
        cache_reader.read_exact(&mut compressed_buffer[..block_comp_len])?;

        if is_oodle {
            debug!("Decompressing with oodle ({} bytes)", block_comp_len);
            decompress_oodle(
                &compressed_buffer,
                block_comp_len,
                &mut decompressed_data[decompressed_pos as usize..],
                block_decomp_len,
            )?;
        } else if block_comp_len == block_decomp_len {
            debug!("Copying ({} bytes)", block_comp_len);
            decompressed_data[decompressed_pos as usize..decompressed_pos + block_decomp_len]
                .copy_from_slice(&compressed_buffer[..block_comp_len]);
        } else {
            debug!("Decompressing with lz4 ({} bytes)", block_comp_len);
            decompress_lz(
                &compressed_buffer,
                block_comp_len,
                &mut decompressed_data[decompressed_pos as usize..],
                block_decomp_len,
            )?;
        }
        debug!("Decompressed {} bytes", block_decomp_len);
        decompressed_pos += block_decomp_len;
    }

    Ok(decompressed_data)
}

pub fn is_oodle_block(cache_reader: &mut File) -> Result<bool> {
    let mut check_magic = [0u8; 1];
    cache_reader.by_ref().read_exact(&mut check_magic)?;
    cache_reader.seek(SeekFrom::Current(-1))?;
    Ok(check_magic[0] == 0x8C)
}

pub fn get_block_lengths(cache_reader: &mut File) -> Result<Option<(usize, usize)>> {
    let mut block_info = [0u8; 8];
    cache_reader.read_exact(&mut block_info)?;

    if block_info[0] != 0x80 || (block_info[7] & 0x0F) != 0x1 {
        cache_reader.seek(SeekFrom::Current(-8))?;
        return Ok(None);
    }

    let num1 = u32::from_be_bytes([block_info[0], block_info[1], block_info[2], block_info[3]]);
    let num2 = u32::from_be_bytes([block_info[4], block_info[5], block_info[6], block_info[7]]);

    let block_comp_len = (num1 >> 2) & 0xFFFFFF;
    let block_decomp_len = (num2 >> 5) & 0xFFFFFF;

    Ok(Some((block_comp_len as usize, block_decomp_len as usize)))
}