<commit_before>use std::ffi::CStr;
use std::ptr::null_mut;
use std::sync::atomic::Ordering;

use arrayvec::ArrayVec;
use lazy_static::lazy_static;
use libc::c_void;

use super::{BwScr, scr};
use super::thiscall::Thiscall;

pub fn open_file_hook(
    bw: &'static BwScr,
    out: *mut scr::FileHandle,
    path: *const u8,
    params: *const scr::OpenParams,
    orig: unsafe extern fn(
        *mut scr::FileHandle, *const u8, *const scr::OpenParams,
    ) -> *mut scr::FileHandle,
) -> *mut scr::FileHandle {
    unsafe {
        let mut buffer = ArrayVec::new();
        let real = real_path(path, params, &mut buffer);
        if let Some(path) = real {
            let is_sd = (*params).file_type == 1;
            if bw.disable_hd && !is_sd {
                if let Some(patched) = check_dummied_out_hd(path) {
                    memory_buffer_to_bw_file_handle(patched, out);
                    return out;
                }
            }
            // This file is so big that it can take hundreds of milliseconds to parse,
            // use empty json array instead.
            if path == b"rez/badnames.json" {
                memory_buffer_to_bw_file_handle(DUMMY_BADNAMES, out);
                return out;
            } else if path == b"anim/skins.json" {
                // Since we don't have a menu where the user can switch
                // skins when game has launched, we shouldn't spend any time
                // loading skins that cannot be switched to.
                let skins = if bw.show_skins.load(Ordering::Relaxed) {
                    if bw.is_carbot.load(Ordering::Relaxed) {
                        CARBOT_SKINS
                    } else {
                        NONCARBOT_SKINS
                    }
                } else {
                    EMPTY_SKINS
                };
                memory_buffer_to_bw_file_handle(skins, out);
                return out;
            } else {
                if bw.is_carbot.load(Ordering::Relaxed) && bw.show_skins.load(Ordering::Relaxed) {
                    // Similarly to skins.json check, don't load the regular HD sprites
                    // if the user has selected carbot
                    if path.starts_with(b"anim/main_") && path.ends_with(b".anim") {
                        memory_buffer_to_bw_file_handle(DUMMY_ANIM, out);
                        return out;
                    }
                }
            }
        }
        orig(out, path, params)
    }
}

static DUMMY_ANIM: &[u8] = include_bytes!("../../files/dummy.anim");
static DUMMY_DDSGRP: &[u8] = &[0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10];
static DUMMY_BADNAMES: &[u8] = br#"[]"#;
static EMPTY_SKINS: &[u8] = br#"{"skins":[]}"#;
static NONCARBOT_SKINS: &[u8] = br#"{"skins":[{"id":1,"name":"PreSale"}]}"#;
static CARBOT_SKINS: &[u8] = br#"{"skins":[{"id":2,"name":"Carbot"}]}"#;

fn check_dummied_out_hd(path: &[u8]) -> Option<&'static [u8]> {
    if path.ends_with(b".anim") {
        // Avoid touching tileset/foliage.anim
        if path.starts_with(b"anim/") {
            return Some(DUMMY_ANIM);
        }
    } else if path.ends_with(b".dds") {
        // Font dds files are used (only) in SD, but they aren't loaded
        // on file param SD.
        if !path.starts_with(b"font/") {
            // Anim happens to have a dds inside it :)
            let dummy_dds = &DUMMY_ANIM[0x174..];
            return Some(dummy_dds);
        }
    } else if path.ends_with(b".dds.vr4") {
        return Some(DUMMY_DDSGRP);
    } else if path.ends_with(b".dds.grp") {
        // Avoid tileset.dds.grps, they need their frames
        if path.starts_with(b"unit/") || path.starts_with(b"effect/") {
            return Some(DUMMY_DDSGRP);
        }
    } else if path == b"anim/skins.json" {
        return Some(EMPTY_SKINS);
    }
    None
}

/// If `params` has a file extension set, it will override whatever
/// extension `path` has.<commit_msg>Always load HD sprites that don't have carbot equivalent<commit_after>use std::ffi::CStr;
use std::ptr::null_mut;
use std::sync::atomic::Ordering;

use arrayvec::ArrayVec;
use lazy_static::lazy_static;
use libc::c_void;

use super::{BwScr, scr};
use super::thiscall::Thiscall;

pub fn open_file_hook(
    bw: &'static BwScr,
    out: *mut scr::FileHandle,
    path: *const u8,
    params: *const scr::OpenParams,
    orig: unsafe extern fn(
        *mut scr::FileHandle, *const u8, *const scr::OpenParams,
    ) -> *mut scr::FileHandle,
) -> *mut scr::FileHandle {
    unsafe {
        let mut buffer = ArrayVec::new();
        let real = real_path(path, params, &mut buffer);
        if let Some(path) = real {
            let is_sd = (*params).file_type == 1;
            if bw.disable_hd && !is_sd {
                if let Some(patched) = check_dummied_out_hd(path) {
                    memory_buffer_to_bw_file_handle(patched, out);
                    return out;
                }
            }
            // This file is so big that it can take hundreds of milliseconds to parse,
            // use empty json array instead.
            if path == b"rez/badnames.json" {
                memory_buffer_to_bw_file_handle(DUMMY_BADNAMES, out);
                return out;
            } else if path == b"anim/skins.json" {
                // Since we don't have a menu where the user can switch
                // skins when game has launched, we shouldn't spend any time
                // loading skins that cannot be switched to.
                let skins = if bw.show_skins.load(Ordering::Relaxed) {
                    if bw.is_carbot.load(Ordering::Relaxed) {
                        CARBOT_SKINS
                    } else {
                        NONCARBOT_SKINS
                    }
                } else {
                    EMPTY_SKINS
                };
                memory_buffer_to_bw_file_handle(skins, out);
                return out;
            } else {
                if bw.is_carbot.load(Ordering::Relaxed) && bw.show_skins.load(Ordering::Relaxed) {
                    // Similarly to skins.json check, don't load all regular HD sprites
                    // if the user has selected carbot
                    // Carbot doesn't have variant for every single sprite,
                    // most notably selection circles but also some (semi-unused?) doodads.
                    // So those anim files still have to be loaded.
                    if path.starts_with(b"anim/main_") && path.ends_with(b".anim") {
                        let load_anim = path.get(b"anim/main_".len()..).and_then(|x| {
                            let num_str = std::str::from_utf8(x.get(..3)?).ok()?;
                            let num = num_str.parse::<u32>().ok()?;
                            MISSING_CARBOT_SPRITES_LOOKUP.get(num as usize).copied()
                        }).unwrap_or(false);
                        if !load_anim {
                            memory_buffer_to_bw_file_handle(DUMMY_ANIM, out);
                            return out;
                        }
                    }
                }
            }
        }
        orig(out, path, params)
    }
}

static DUMMY_ANIM: &[u8] = include_bytes!("../../files/dummy.anim");
static DUMMY_DDSGRP: &[u8] = &[0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10];
static DUMMY_BADNAMES: &[u8] = br#"[]"#;
static EMPTY_SKINS: &[u8] = br#"{"skins":[]}"#;
static NONCARBOT_SKINS: &[u8] = br#"{"skins":[{"id":1,"name":"PreSale"}]}"#;
static CARBOT_SKINS: &[u8] = br#"{"skins":[{"id":2,"name":"Carbot"}]}"#;

static MISSING_CARBOT_SPRITES_LOOKUP: [bool; 999] = missing_carbot_sprites();
const MISSING_CARBOT_SPRITES_LIST: &[u32] = &[
    106, 503, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577,
    578, 579, 580, 581, 588, 611, 613, 615, 617, 619, 621, 623, 625, 627, 629, 631, 633, 635, 637,
    639, 666, 692, 694, 696, 698, 705, 707, 709, 711, 756, 784, 792, 837, 839, 861, 873, 893, 905,
    908, 910, 932, 965, 972,
];

const fn missing_carbot_sprites() -> [bool; 999] {
    let mut result = [false; 999];
    let mut i = 0;
    while i < MISSING_CARBOT_SPRITES_LIST.len() {
        result[MISSING_CARBOT_SPRITES_LIST[i] as usize] = true;
        i += 1;
    }
    result
}

fn check_dummied_out_hd(path: &[u8]) -> Option<&'static [u8]> {
    if path.ends_with(b".anim") {
        // Avoid touching tileset/foliage.anim
        if path.starts_with(b"anim/") {
            return Some(DUMMY_ANIM);
        }
    } else if path.ends_with(b".dds") {
        // Font dds files are used (only) in SD, but they aren't loaded
        // on file param SD.
        if !path.starts_with(b"font/") {
            // Anim happens to have a dds inside it :)
            let dummy_dds = &DUMMY_ANIM[0x174..];
            return Some(dummy_dds);
        }
    } else if path.ends_with(b".dds.vr4") {
        return Some(DUMMY_DDSGRP);
    } else if path.ends_with(b".dds.grp") {
        // Avoid tileset.dds.grps, they need their frames
        if path.starts_with(b"unit/") || path.starts_with(b"effect/") {
            return Some(DUMMY_DDSGRP);
        }
    } else if path == b"anim/skins.json" {
        return Some(EMPTY_SKINS);
    }
    None
}

/// If `params` has a file extension set, it will override whatever
/// extension `path` has.
{'fraction_non_alphanumeric': 0.13049095607235142, 'fraction_numerical': 0.03090008613264427, 'mean_word_length': 1.8678604507564063, 'pattern_counts': {'":': 14, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5803800', 'n_tokens_mistral': 3387, 'n_tokens_neox': 3080, 'n_words': 740}