use libc::{c_char, c_void};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fs;
use std::path::Path;
use std::ptr;
use std::sync::{LazyLock, Mutex};
use wow_mpq::{
AddFileOptions, Archive, ArchiveBuilder, AttributesOption, FileEntry, FormatVersion,
ListfileOption, MutableArchive,
};
pub type HANDLE = *mut c_void;
pub const INVALID_HANDLE_VALUE: HANDLE = ptr::null_mut();
static NEXT_HANDLE: LazyLock<Mutex<usize>> = LazyLock::new(|| Mutex::new(1));
static ARCHIVES: LazyLock<Mutex<HashMap<usize, ArchiveHandle>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static FILES: LazyLock<Mutex<HashMap<usize, FileHandle>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static FIND_HANDLES: LazyLock<Mutex<HashMap<usize, FindHandle>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
thread_local! {
static LAST_ERROR: RefCell<u32> = const { RefCell::new(ERROR_SUCCESS) };
static LOCALE: RefCell<u32> = const { RefCell::new(LOCALE_NEUTRAL) };
}
#[allow(clippy::large_enum_variant)]
enum ArchiveHandle {
ReadOnly {
archive: Archive,
path: String,
},
Mutable {
archive: MutableArchive,
path: String,
},
}
struct FileHandle {
archive_handle: usize,
filename: String,
data: Vec<u8>,
position: usize,
size: u64,
}
impl ArchiveHandle {
fn archive(&self) -> &Archive {
match self {
ArchiveHandle::ReadOnly { archive, .. } => archive,
ArchiveHandle::Mutable { archive, .. } => archive.archive(),
}
}
fn mutable_archive(&mut self) -> Option<&mut MutableArchive> {
match self {
ArchiveHandle::ReadOnly { .. } => None,
ArchiveHandle::Mutable { archive, .. } => Some(archive),
}
}
fn path(&self) -> &str {
match self {
ArchiveHandle::ReadOnly { path, .. } => path,
ArchiveHandle::Mutable { path, .. } => path,
}
}
}
const ERROR_SUCCESS: u32 = 0;
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_INVALID_HANDLE: u32 = 6;
const _ERROR_NOT_ENOUGH_MEMORY: u32 = 8;
const ERROR_INVALID_PARAMETER: u32 = 87;
const ERROR_INSUFFICIENT_BUFFER: u32 = 122;
const ERROR_ALREADY_EXISTS: u32 = 183;
const ERROR_FILE_CORRUPT: u32 = 1392;
const ERROR_NOT_SUPPORTED: u32 = 50;
const LOCALE_NEUTRAL: u32 = 0;
const _SFILE_OPEN_FROM_MPQ: u32 = 0x00000000;
const SFILE_VERIFY_SECTOR_CRC: u32 = 0x01;
const SFILE_VERIFY_FILE_CRC: u32 = 0x02;
const SFILE_VERIFY_FILE_MD5: u32 = 0x04;
const _SFILE_VERIFY_RAW_MD5: u32 = 0x08;
const SFILE_VERIFY_SIGNATURE: u32 = 0x10;
const SFILE_VERIFY_ALL_FILES: u32 = 0x20;
const SFILE_VERIFY_ALL: u32 = 0xFF;
const SFILE_INFO_ARCHIVE_SIZE: u32 = 1;
const SFILE_INFO_HASH_TABLE_SIZE: u32 = 2;
const SFILE_INFO_BLOCK_TABLE_SIZE: u32 = 3;
const SFILE_INFO_SECTOR_SIZE: u32 = 4;
const _SFILE_INFO_NUM_FILES: u32 = 5;
const _SFILE_INFO_STREAM_FLAGS: u32 = 6;
const SFILE_INFO_FILE_SIZE: u32 = 7;
const _SFILE_INFO_COMPRESSED_SIZE: u32 = 8;
const _SFILE_INFO_FLAGS: u32 = 9;
const SFILE_INFO_POSITION: u32 = 10;
const _SFILE_INFO_KEY: u32 = 11;
const _SFILE_INFO_KEY_UNFIXED: u32 = 12;
const _MPQ_OPEN_NO_LISTFILE: u32 = 0x0001;
const _MPQ_OPEN_NO_ATTRIBUTES: u32 = 0x0002;
const _MPQ_OPEN_FORCE_MPQ_V1: u32 = 0x0004;
const _MPQ_OPEN_CHECK_SECTOR_CRC: u32 = 0x0008;
const CREATE_NEW: u32 = 1;
const CREATE_ALWAYS: u32 = 2;
const OPEN_EXISTING: u32 = 3;
const OPEN_ALWAYS: u32 = 4;
const TRUNCATE_EXISTING: u32 = 5;
const _MPQ_CREATE_ARCHIVE_V1: u32 = 0x00000000;
const _MPQ_CREATE_ARCHIVE_V2: u32 = 0x01000000;
const _MPQ_CREATE_ARCHIVE_V3: u32 = 0x02000000;
const _MPQ_CREATE_ARCHIVE_V4: u32 = 0x03000000;
fn set_last_error(error: u32) {
LAST_ERROR.with(|e| *e.borrow_mut() = error);
}
fn handle_to_id(handle: HANDLE) -> Option<usize> {
if handle.is_null() {
None
} else {
Some(handle as usize)
}
}
fn id_to_handle(id: usize) -> HANDLE {
id as HANDLE
}
#[no_mangle]
pub unsafe extern "C" fn SFileOpenArchive(
filename: *const c_char,
_priority: u32, _flags: u32, handle: *mut HANDLE,
) -> bool {
if filename.is_null() || handle.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
match Archive::open(filename_str) {
Ok(archive) => {
let mut next_id = NEXT_HANDLE.lock().unwrap();
let handle_id = *next_id;
*next_id += 1;
drop(next_id);
let archive_handle = ArchiveHandle::ReadOnly {
archive,
path: filename_str.to_string(),
};
ARCHIVES.lock().unwrap().insert(handle_id, archive_handle);
*handle = id_to_handle(handle_id);
set_last_error(ERROR_SUCCESS);
true
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::InvalidFormat(_) => ERROR_FILE_CORRUPT,
wow_mpq::Error::Io(_) => ERROR_ACCESS_DENIED,
_ => ERROR_FILE_CORRUPT,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileCreateArchive(
filename: *const c_char,
creation_disposition: u32,
hash_table_size: u32,
handle: *mut HANDLE,
) -> bool {
if filename.is_null() || handle.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
if !wow_mpq::is_power_of_two(hash_table_size) || hash_table_size < 4 {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let file_exists = Path::new(filename_str).exists();
match creation_disposition {
CREATE_NEW => {
if file_exists {
set_last_error(ERROR_ALREADY_EXISTS);
return false;
}
}
CREATE_ALWAYS => {
}
OPEN_EXISTING => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
OPEN_ALWAYS => {
if file_exists {
return SFileOpenArchive(filename, 0, 0, handle);
}
}
TRUNCATE_EXISTING => {
if !file_exists {
set_last_error(ERROR_FILE_NOT_FOUND);
return false;
}
}
_ => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
}
let version = FormatVersion::V2;
let result = ArchiveBuilder::new()
.version(version)
.listfile_option(ListfileOption::Generate)
.build(filename_str);
match result {
Ok(_) => {
SFileOpenArchive(filename, 0, 0, handle)
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::InvalidFormat(_) => ERROR_INVALID_PARAMETER,
wow_mpq::Error::Io(_) => ERROR_ACCESS_DENIED,
_ => ERROR_ACCESS_DENIED,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub extern "C" fn SFileCloseArchive(handle: HANDLE) -> bool {
if let Some(handle_id) = handle_to_id(handle) {
FILES
.lock()
.unwrap()
.retain(|_, file| file.archive_handle != handle_id);
if ARCHIVES.lock().unwrap().remove(&handle_id).is_some() {
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INVALID_HANDLE);
false
}
} else {
set_last_error(ERROR_INVALID_HANDLE);
false
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileOpenFileEx(
archive: HANDLE,
filename: *const c_char,
_search_scope: u32,
file_handle: *mut HANDLE,
) -> bool {
if filename.is_null() || file_handle.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get_mut(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let (file_info_opt, read_result) = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => match archive.find_file(filename_str) {
Ok(Some(file_info)) => {
let read_result = archive.read_file(filename_str);
(Some(file_info), read_result)
}
Ok(None) => (
None,
Err(wow_mpq::Error::FileNotFound(filename_str.to_string())),
),
Err(e) => (None, Err(e)),
},
ArchiveHandle::Mutable { archive, .. } => match archive.find_file(filename_str) {
Ok(Some(file_info)) => {
let read_result = archive.read_file(filename_str);
(Some(file_info), read_result)
}
Ok(None) => (
None,
Err(wow_mpq::Error::FileNotFound(filename_str.to_string())),
),
Err(e) => (None, Err(e)),
},
};
if let Some(file_info) = file_info_opt {
match read_result {
Ok(data) => {
let mut next_id = NEXT_HANDLE.lock().unwrap();
let file_id = *next_id;
*next_id += 1;
drop(next_id);
let file = FileHandle {
archive_handle: archive_id,
filename: filename_str.to_string(),
data,
position: 0,
size: file_info.file_size,
};
FILES.lock().unwrap().insert(file_id, file);
*file_handle = id_to_handle(file_id);
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
false
}
}
} else {
set_last_error(ERROR_FILE_NOT_FOUND);
false
}
}
#[no_mangle]
pub extern "C" fn SFileCloseFile(file: HANDLE) -> bool {
if let Some(file_id) = handle_to_id(file) {
if FILES.lock().unwrap().remove(&file_id).is_some() {
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INVALID_HANDLE);
false
}
} else {
set_last_error(ERROR_INVALID_HANDLE);
false
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileReadFile(
file: HANDLE,
buffer: *mut c_void,
to_read: u32,
read: *mut u32,
_overlapped: *mut c_void, ) -> bool {
if buffer.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(file_id) = handle_to_id(file) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let mut files = FILES.lock().unwrap();
let Some(file_handle) = files.get_mut(&file_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let remaining = file_handle.data.len().saturating_sub(file_handle.position);
let bytes_to_read = (to_read as usize).min(remaining);
if bytes_to_read == 0 {
if !read.is_null() {
*read = 0;
}
set_last_error(ERROR_SUCCESS);
return true;
}
let src = &file_handle.data[file_handle.position..file_handle.position + bytes_to_read];
std::ptr::copy_nonoverlapping(src.as_ptr(), buffer as *mut u8, bytes_to_read);
file_handle.position += bytes_to_read;
if !read.is_null() {
*read = bytes_to_read as u32;
}
set_last_error(ERROR_SUCCESS);
true
}
#[no_mangle]
pub unsafe extern "C" fn SFileGetFileSize(file: HANDLE, high: *mut u32) -> u32 {
let Some(file_id) = handle_to_id(file) else {
set_last_error(ERROR_INVALID_HANDLE);
return 0xFFFFFFFF; };
let files = FILES.lock().unwrap();
let Some(file_handle) = files.get(&file_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return 0xFFFFFFFF;
};
let size = file_handle.size;
if !high.is_null() {
*high = (size >> 32) as u32;
}
set_last_error(ERROR_SUCCESS);
(size & 0xFFFFFFFF) as u32
}
#[no_mangle]
pub unsafe extern "C" fn SFileSetFilePointer(
file: HANDLE,
file_pos: i32,
file_pos_high: *mut i32,
move_method: u32, ) -> u32 {
let Some(file_id) = handle_to_id(file) else {
set_last_error(ERROR_INVALID_HANDLE);
return 0xFFFFFFFF; };
let mut files = FILES.lock().unwrap();
let Some(file_handle) = files.get_mut(&file_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return 0xFFFFFFFF;
};
let mut offset = file_pos as i64;
if !file_pos_high.is_null() {
let high = *file_pos_high as i64;
offset |= high << 32;
}
let new_pos = match move_method {
0 => offset as usize, 1 => (file_handle.position as i64 + offset) as usize, 2 => (file_handle.data.len() as i64 + offset) as usize, _ => {
set_last_error(ERROR_INVALID_PARAMETER);
return 0xFFFFFFFF;
}
};
file_handle.position = new_pos.min(file_handle.data.len());
let pos = file_handle.position as u64;
if !file_pos_high.is_null() {
*file_pos_high = (pos >> 32) as i32;
}
set_last_error(ERROR_SUCCESS);
(pos & 0xFFFFFFFF) as u32
}
#[no_mangle]
pub unsafe extern "C" fn SFileHasFile(archive: HANDLE, filename: *const c_char) -> bool {
if filename.is_null() {
return false;
}
let Some(archive_id) = handle_to_id(archive) else {
return false;
};
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => return false,
};
let archives = ARCHIVES.lock().unwrap();
if let Some(archive_handle) = archives.get(&archive_id) {
matches!(
archive_handle.archive().find_file(filename_str),
Ok(Some(_))
)
} else {
false
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileGetFileInfo(
file_or_archive: HANDLE,
info_class: u32,
buffer: *mut c_void,
buffer_size: u32,
size_needed: *mut u32,
) -> bool {
if buffer.is_null() && buffer_size > 0 {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let handle_id = match handle_to_id(file_or_archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
if let Some(file_handle) = FILES.lock().unwrap().get(&handle_id) {
return get_file_info(file_handle, info_class, buffer, buffer_size, size_needed);
}
if let Some(archive_handle) = ARCHIVES.lock().unwrap().get(&handle_id) {
return get_archive_info(archive_handle, info_class, buffer, buffer_size, size_needed);
}
set_last_error(ERROR_INVALID_HANDLE);
false
}
unsafe fn get_file_info(
file_handle: &FileHandle,
info_class: u32,
buffer: *mut c_void,
buffer_size: u32,
size_needed: *mut u32,
) -> bool {
match info_class {
SFILE_INFO_FILE_SIZE => {
let needed = 8u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u64) = file_handle.size;
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
SFILE_INFO_POSITION => {
let needed = 8u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u64) = file_handle.position as u64;
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
_ => {
set_last_error(ERROR_NOT_SUPPORTED);
false
}
}
}
unsafe fn get_archive_info(
archive_handle: &ArchiveHandle,
info_class: u32,
buffer: *mut c_void,
buffer_size: u32,
size_needed: *mut u32,
) -> bool {
let header = archive_handle.archive().header();
match info_class {
SFILE_INFO_ARCHIVE_SIZE => {
let needed = 8u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u64) = header.get_archive_size();
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
SFILE_INFO_HASH_TABLE_SIZE => {
let needed = 4u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u32) = header.hash_table_size;
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
SFILE_INFO_BLOCK_TABLE_SIZE => {
let needed = 4u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u32) = header.block_table_size;
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
SFILE_INFO_SECTOR_SIZE => {
let needed = 4u32;
if !size_needed.is_null() {
*size_needed = needed;
}
if buffer_size >= needed {
*(buffer as *mut u32) = header.sector_size() as u32;
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
false
}
}
_ => {
set_last_error(ERROR_NOT_SUPPORTED);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileGetArchiveName(
archive: HANDLE,
buffer: *mut c_char,
buffer_size: u32,
) -> bool {
if buffer.is_null() || buffer_size == 0 {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let c_path = match CString::new(archive_handle.path()) {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let path_bytes = c_path.as_bytes_with_nul();
if path_bytes.len() > buffer_size as usize {
set_last_error(ERROR_INSUFFICIENT_BUFFER);
return false;
}
std::ptr::copy_nonoverlapping(path_bytes.as_ptr(), buffer as *mut u8, path_bytes.len());
set_last_error(ERROR_SUCCESS);
true
}
#[no_mangle]
pub unsafe extern "C" fn SFileEnumFiles(
archive: HANDLE,
search_mask: *const c_char,
_list_file: *const c_char, callback: Option<extern "C" fn(*const c_char, *mut c_void) -> bool>,
user_data: *mut c_void,
) -> bool {
let Some(callback_fn) = callback else {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
};
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let pattern = if search_mask.is_null() {
"*".to_string()
} else {
match CStr::from_ptr(search_mask).to_str() {
Ok(s) => s.to_string(),
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
}
};
let mut archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get_mut(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let file_list = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.list(),
ArchiveHandle::Mutable { archive, .. } => archive.list(),
};
match file_list {
Ok(entries) => {
for entry in entries {
if pattern == "*" || entry.name.contains(&pattern.replace("*", "")) {
let c_name = match CString::new(entry.name.as_str()) {
Ok(s) => s,
Err(_) => continue,
};
if !callback_fn(c_name.as_ptr(), user_data) {
break;
}
}
}
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
set_last_error(ERROR_SUCCESS);
true
}
}
}
#[no_mangle]
pub extern "C" fn SFileSetLocale(locale: u32) -> u32 {
let old_locale = LOCALE.with(|l| {
let old = *l.borrow();
*l.borrow_mut() = locale;
old
});
old_locale
}
#[no_mangle]
pub extern "C" fn SFileGetLocale() -> u32 {
LOCALE.with(|l| *l.borrow())
}
#[no_mangle]
pub extern "C" fn SFileGetLastError() -> u32 {
LAST_ERROR.with(|e| *e.borrow())
}
#[no_mangle]
pub extern "C" fn SFileSetLastError(error: u32) {
set_last_error(error);
}
#[no_mangle]
pub unsafe extern "C" fn SFileGetFileName(file: HANDLE, buffer: *mut c_char) -> bool {
if buffer.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(file_id) = handle_to_id(file) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let files = FILES.lock().unwrap();
let Some(file_handle) = files.get(&file_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let c_name = match CString::new(file_handle.filename.as_str()) {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
std::ptr::copy_nonoverlapping(c_name.as_ptr(), buffer, c_name.as_bytes_with_nul().len());
set_last_error(ERROR_SUCCESS);
true
}
#[no_mangle]
pub unsafe extern "C" fn SFileExtractFile(
archive: HANDLE,
filename: *const c_char,
local_filename: *const c_char,
_search_scope: u32, ) -> bool {
if filename.is_null() || local_filename.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let source_filename = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let dest_filename = match CStr::from_ptr(local_filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get_mut(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let file_data = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.read_file(source_filename),
ArchiveHandle::Mutable { archive, .. } => archive.read_file(source_filename),
};
match file_data {
Ok(data) => {
let dest_path = Path::new(dest_filename);
if let Some(parent) = dest_path.parent() {
if fs::create_dir_all(parent).is_err() {
set_last_error(ERROR_ACCESS_DENIED);
return false;
}
}
match fs::write(dest_path, data) {
Ok(_) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
set_last_error(ERROR_ACCESS_DENIED);
false
}
}
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::InvalidFormat(_) => ERROR_FILE_CORRUPT,
wow_mpq::Error::Io(_) => ERROR_ACCESS_DENIED,
_ => ERROR_FILE_CORRUPT,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileVerifyFile(
archive: HANDLE,
filename: *const c_char,
flags: u32,
) -> bool {
if filename.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get_mut(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let file_info = match archive_handle.archive().find_file(filename_str) {
Ok(Some(info)) => info,
Ok(None) => {
set_last_error(ERROR_FILE_NOT_FOUND);
return false;
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
};
let verify_flags = if flags == 0 { SFILE_VERIFY_ALL } else { flags };
if (verify_flags & SFILE_VERIFY_SECTOR_CRC) != 0 && file_info.has_sector_crc() {
let read_result = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.read_file(filename_str),
ArchiveHandle::Mutable { archive, .. } => archive.read_file(filename_str),
};
match read_result {
Ok(_) => {
}
Err(wow_mpq::Error::ChecksumMismatch { .. }) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
}
if (verify_flags & SFILE_VERIFY_FILE_CRC) != 0 {
match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => {
let _ = archive.load_attributes();
}
ArchiveHandle::Mutable { archive, .. } => {
let _ = archive.load_attributes();
}
}
if let Some(attrs) = archive_handle
.archive()
.get_file_attributes(file_info.block_index)
{
if let Some(expected_crc) = attrs.crc32 {
let read_result = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.read_file(filename_str),
ArchiveHandle::Mutable { archive, .. } => archive.read_file(filename_str),
};
match read_result {
Ok(data) => {
let actual_crc = crc32fast::hash(&data);
if actual_crc != expected_crc {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
}
}
}
if (verify_flags & SFILE_VERIFY_FILE_MD5) != 0 {
match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => {
let _ = archive.load_attributes();
}
ArchiveHandle::Mutable { archive, .. } => {
let _ = archive.load_attributes();
}
}
if let Some(attrs) = archive_handle
.archive()
.get_file_attributes(file_info.block_index)
{
if let Some(expected_md5) = attrs.md5 {
let read_result = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.read_file(filename_str),
ArchiveHandle::Mutable { archive, .. } => archive.read_file(filename_str),
};
match read_result {
Ok(data) => {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(&data);
let actual_md5: [u8; 16] = hasher.finalize().into();
if actual_md5 != expected_md5 {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
}
}
}
set_last_error(ERROR_SUCCESS);
true
}
#[no_mangle]
pub unsafe extern "C" fn SFileVerifyArchive(archive: HANDLE, flags: u32) -> bool {
let Some(archive_id) = handle_to_id(archive) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let mut archives = ARCHIVES.lock().unwrap();
let Some(archive_handle) = archives.get_mut(&archive_id) else {
set_last_error(ERROR_INVALID_HANDLE);
return false;
};
let verify_flags = if flags == 0 {
SFILE_VERIFY_SIGNATURE
} else {
flags
};
if (verify_flags & SFILE_VERIFY_SIGNATURE) != 0 {
let signature_result = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.verify_signature(),
ArchiveHandle::Mutable { archive, .. } => archive.verify_signature(),
};
match signature_result {
Ok(wow_mpq::SignatureStatus::WeakValid) | Ok(wow_mpq::SignatureStatus::StrongValid) => {
}
Ok(wow_mpq::SignatureStatus::None) => {
}
Ok(wow_mpq::SignatureStatus::WeakInvalid)
| Ok(wow_mpq::SignatureStatus::StrongInvalid) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
Ok(wow_mpq::SignatureStatus::StrongNoKey) => {
}
Err(_) => {
set_last_error(ERROR_FILE_CORRUPT);
return false;
}
}
}
if (verify_flags & SFILE_VERIFY_ALL_FILES) != 0 {
let file_list = match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => archive.list(),
ArchiveHandle::Mutable { archive, .. } => archive.list(),
};
let file_list = file_list.unwrap_or_default();
for file_entry in file_list {
if file_entry.name.starts_with('(') && file_entry.name.ends_with(')') {
continue;
}
if file_entry.name.ends_with('/') || file_entry.name.ends_with('\\') {
continue;
}
let file_verify_flags = if file_entry.has_sector_crc() {
SFILE_VERIFY_SECTOR_CRC
} else {
0
};
let all_verify_flags =
file_verify_flags | SFILE_VERIFY_FILE_CRC | SFILE_VERIFY_FILE_MD5;
unsafe {
let filename_cstr = match std::ffi::CString::new(file_entry.name.as_str()) {
Ok(s) => s,
Err(_) => continue, };
if !SFileVerifyFile(
id_to_handle(archive_id),
filename_cstr.as_ptr(),
all_verify_flags,
) {
let last_error = SFileGetLastError();
if last_error == ERROR_FILE_CORRUPT {
return false;
}
}
}
}
}
set_last_error(ERROR_SUCCESS);
true
}
#[no_mangle]
pub unsafe extern "C" fn SFileAddFileEx(
archive: HANDLE,
filename: *const c_char,
archived_name: *const c_char,
flags: u32,
compression: u32,
_compression_next: u32,
) -> bool {
if archive.is_null() || filename.is_null() || archived_name.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let archive_id = match handle_to_id(archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let archived_name_str = match CStr::from_ptr(archived_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let archive_handle = match archives.get_mut(&archive_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mutable_archive = match archive_handle.mutable_archive() {
Some(archive) => archive,
None => {
set_last_error(ERROR_ACCESS_DENIED);
return false;
}
};
let mut options = AddFileOptions::new();
use wow_mpq::compression::CompressionMethod;
let compression_method = match compression {
0 => CompressionMethod::None,
0x02 => CompressionMethod::Zlib,
0x08 => CompressionMethod::PKWare,
0x10 => CompressionMethod::BZip2,
0x20 => CompressionMethod::Sparse,
0x40 => CompressionMethod::AdpcmMono,
0x80 => CompressionMethod::AdpcmStereo,
0x12 => CompressionMethod::Lzma,
_ => CompressionMethod::Zlib, };
options = options.compression(compression_method);
const MPQ_FILE_ENCRYPTED: u32 = 0x00010000;
const MPQ_FILE_FIX_KEY: u32 = 0x00020000;
const MPQ_FILE_REPLACEEXISTING: u32 = 0x80000000;
if (flags & MPQ_FILE_ENCRYPTED) != 0 {
options = options.encrypt();
}
if (flags & MPQ_FILE_FIX_KEY) != 0 {
options = options.fix_key();
}
if (flags & MPQ_FILE_REPLACEEXISTING) != 0 {
options = options.replace_existing(true);
}
match mutable_archive.add_file(filename_str, archived_name_str, options) {
Ok(()) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::FileExists(_) => ERROR_ALREADY_EXISTS,
wow_mpq::Error::Io(_) => ERROR_ACCESS_DENIED,
_ => ERROR_ACCESS_DENIED,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileAddFile(
archive: HANDLE,
filename: *const c_char,
archived_name: *const c_char,
flags: u32,
) -> bool {
SFileAddFileEx(archive, filename, archived_name, flags, 0x02, 0) }
#[no_mangle]
pub unsafe extern "C" fn SFileRemoveFile(
archive: HANDLE,
filename: *const c_char,
_search_scope: u32,
) -> bool {
if archive.is_null() || filename.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let archive_id = match handle_to_id(archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let archive_handle = match archives.get_mut(&archive_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mutable_archive = match archive_handle.mutable_archive() {
Some(archive) => archive,
None => {
set_last_error(ERROR_ACCESS_DENIED);
return false;
}
};
match mutable_archive.remove_file(filename_str) {
Ok(()) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
_ => ERROR_ACCESS_DENIED,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileRenameFile(
archive: HANDLE,
old_filename: *const c_char,
new_filename: *const c_char,
) -> bool {
if archive.is_null() || old_filename.is_null() || new_filename.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let archive_id = match handle_to_id(archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let old_filename_str = match CStr::from_ptr(old_filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let new_filename_str = match CStr::from_ptr(new_filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let archive_handle = match archives.get_mut(&archive_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mutable_archive = match archive_handle.mutable_archive() {
Some(archive) => archive,
None => {
set_last_error(ERROR_ACCESS_DENIED);
return false;
}
};
match mutable_archive.rename_file(old_filename_str, new_filename_str) {
Ok(()) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::FileExists(_) => ERROR_ALREADY_EXISTS,
_ => ERROR_ACCESS_DENIED,
};
set_last_error(error_code);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileFlushArchive(archive: HANDLE) -> bool {
if archive.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let archive_id = match handle_to_id(archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let archive_handle = match archives.get_mut(&archive_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mutable_archive = match archive_handle.mutable_archive() {
Some(archive) => archive,
None => {
set_last_error(ERROR_SUCCESS); return true;
}
};
match mutable_archive.flush() {
Ok(()) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
set_last_error(ERROR_ACCESS_DENIED);
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileCompactArchive(
archive: HANDLE,
_listfile: *const c_char,
_reserved: bool,
) -> bool {
if archive.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let archive_id = match handle_to_id(archive) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mut archives = ARCHIVES.lock().unwrap();
let archive_handle = match archives.get_mut(&archive_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mutable_archive = match archive_handle.mutable_archive() {
Some(archive) => archive,
None => {
set_last_error(ERROR_ACCESS_DENIED);
return false;
}
};
match mutable_archive.compact() {
Ok(()) => {
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
set_last_error(ERROR_NOT_SUPPORTED); false
}
}
}
#[repr(C)]
pub struct SFILE_CREATE_MPQ {
pub cb_size: u32,
pub mpq_version: u32,
pub user_data: *mut c_void,
pub cb_user_data: u32,
pub stream_flags: u32,
pub file_flags_1: u32,
pub file_flags_2: u32,
pub file_flags_3: u32,
pub attr_flags: u32,
pub sector_size: u32,
pub raw_chunk_size: u32,
pub max_file_count: u32,
}
const _MPQ_FILE_DEFAULT_INTERNAL: u32 = 0xFFFFFFFF;
const MPQ_ATTRIBUTE_CRC32: u32 = 0x00000001;
const _MPQ_ATTRIBUTE_FILETIME: u32 = 0x00000002;
const MPQ_ATTRIBUTE_MD5: u32 = 0x00000004;
const _MPQ_ATTRIBUTE_PATCH_BIT: u32 = 0x00000008;
const _MPQ_ATTRIBUTE_ALL: u32 = 0x0000000F;
#[repr(C)]
pub struct SFILE_FIND_DATA {
pub c_file_name: [c_char; 260], pub sz_plain_name: *mut c_char,
pub hash_index: u32,
pub block_index: u32,
pub file_size: u32,
pub file_flags: u32,
pub comp_size: u32,
pub file_time_lo: u32,
pub file_time_hi: u32,
pub lc_locale: u32, }
struct FindHandle {
archive_handle: usize,
file_list: Vec<FileEntry>,
current_index: usize,
search_mask: String,
}
impl FindHandle {
fn matches_mask(&self, filename: &str) -> bool {
let mask = &self.search_mask;
if mask == "*" || mask == "*.*" {
return true;
}
wildcard_match(&mask.to_lowercase(), &filename.to_lowercase())
}
}
fn wildcard_match(pattern: &str, text: &str) -> bool {
let mut pattern_chars = pattern.chars().peekable();
let mut text_chars = text.chars().peekable();
while let Some(p) = pattern_chars.peek().copied() {
match p {
'*' => {
pattern_chars.next();
if pattern_chars.peek().is_none() {
return true;
}
let remaining_pattern: String = pattern_chars.collect();
let mut remaining_text = text_chars.clone();
loop {
let remaining_text_str: String = remaining_text.clone().collect();
if wildcard_match(&remaining_pattern, &remaining_text_str) {
return true;
}
if remaining_text.next().is_none() {
return false;
}
}
}
'?' => {
pattern_chars.next();
if text_chars.next().is_none() {
return false;
}
}
_ => {
pattern_chars.next();
match text_chars.next() {
Some(t) if t == p => continue,
_ => return false,
}
}
}
}
text_chars.next().is_none()
}
#[no_mangle]
pub unsafe extern "C" fn SFileFindFirstFile(
h_mpq: HANDLE,
sz_mask: *const c_char,
lp_find_file_data: *mut SFILE_FIND_DATA,
_sz_list_file: *const c_char, ) -> HANDLE {
if lp_find_file_data.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
let archive_id = match handle_to_id(h_mpq) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return INVALID_HANDLE_VALUE;
}
};
let search_mask = if sz_mask.is_null() {
"*".to_string()
} else {
match CStr::from_ptr(sz_mask).to_str() {
Ok(s) => s.to_string(),
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
}
};
let file_list = {
let mut archives = ARCHIVES.lock().unwrap();
match archives.get_mut(&archive_id) {
Some(archive_handle) => {
match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => {
match archive.list() {
Ok(list) => list,
Err(_) => {
match archive.list_all() {
Ok(list) => list,
Err(_) => {
set_last_error(ERROR_FILE_NOT_FOUND);
return INVALID_HANDLE_VALUE;
}
}
}
}
}
ArchiveHandle::Mutable { archive, .. } => match archive.list() {
Ok(list) => list,
Err(_) => {
set_last_error(ERROR_FILE_NOT_FOUND);
return INVALID_HANDLE_VALUE;
}
},
}
}
None => {
set_last_error(ERROR_INVALID_HANDLE);
return INVALID_HANDLE_VALUE;
}
}
};
let mut find_handle = FindHandle {
archive_handle: archive_id,
file_list,
current_index: 0,
search_mask,
};
let found_file = loop {
if find_handle.current_index >= find_handle.file_list.len() {
set_last_error(ERROR_FILE_NOT_FOUND);
return INVALID_HANDLE_VALUE;
}
let file = &find_handle.file_list[find_handle.current_index];
if find_handle.matches_mask(&file.name) {
break Some(file);
}
find_handle.current_index += 1;
};
if let Some(file) = found_file {
fill_find_data(lp_find_file_data, file, archive_id);
let mut next_id = NEXT_HANDLE.lock().unwrap();
let handle_id = *next_id;
*next_id += 1;
drop(next_id);
find_handle.current_index += 1; FIND_HANDLES.lock().unwrap().insert(handle_id, find_handle);
set_last_error(ERROR_SUCCESS);
id_to_handle(handle_id)
} else {
set_last_error(ERROR_FILE_NOT_FOUND);
INVALID_HANDLE_VALUE
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileFindNextFile(
h_find: HANDLE,
lp_find_file_data: *mut SFILE_FIND_DATA,
) -> bool {
if lp_find_file_data.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let handle_id = match handle_to_id(h_find) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let mut find_handles = FIND_HANDLES.lock().unwrap();
let find_handle = match find_handles.get_mut(&handle_id) {
Some(handle) => handle,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
let found_file = loop {
if find_handle.current_index >= find_handle.file_list.len() {
set_last_error(ERROR_NO_MORE_FILES);
return false;
}
let file = &find_handle.file_list[find_handle.current_index];
find_handle.current_index += 1;
if find_handle.matches_mask(&file.name) {
break Some(file);
}
};
if let Some(file) = found_file {
fill_find_data(lp_find_file_data, file, find_handle.archive_handle);
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_NO_MORE_FILES);
false
}
}
#[no_mangle]
pub unsafe extern "C" fn SFileFindClose(h_find: HANDLE) -> bool {
let handle_id = match handle_to_id(h_find) {
Some(id) => id,
None => {
set_last_error(ERROR_INVALID_HANDLE);
return false;
}
};
if FIND_HANDLES.lock().unwrap().remove(&handle_id).is_some() {
set_last_error(ERROR_SUCCESS);
true
} else {
set_last_error(ERROR_INVALID_HANDLE);
false
}
}
unsafe fn fill_find_data(
find_data: *mut SFILE_FIND_DATA,
file_entry: &FileEntry,
archive_id: usize,
) {
let find_data = &mut *find_data;
let name_bytes = file_entry.name.as_bytes();
let copy_len = name_bytes.len().min(259);
find_data.c_file_name.fill(0);
for (i, &byte) in name_bytes.iter().take(copy_len).enumerate() {
find_data.c_file_name[i] = byte as c_char;
}
let plain_name_offset = file_entry.name.rfind('\\').map(|pos| pos + 1).unwrap_or(0);
find_data.sz_plain_name = find_data.c_file_name.as_mut_ptr().add(plain_name_offset);
find_data.hash_index = if let Some((hash_idx, _)) = file_entry.table_indices {
hash_idx as u32
} else {
0xFFFFFFFF
};
find_data.block_index = if let Some((_, Some(block_idx))) = file_entry.table_indices {
block_idx as u32
} else {
0xFFFFFFFF
};
find_data.file_size = file_entry.size as u32;
find_data.file_flags = file_entry.flags;
find_data.comp_size = file_entry.compressed_size as u32;
find_data.file_time_lo = 0; find_data.file_time_hi = 0;
find_data.lc_locale = 0;
let mut archives = ARCHIVES.lock().unwrap();
if let Some(archive_handle) = archives.get_mut(&archive_id) {
match archive_handle {
ArchiveHandle::ReadOnly { archive, .. } => {
if let Some((_, Some(block_idx))) = file_entry.table_indices {
if let Some(attrs) = archive.get_file_attributes(block_idx) {
if let Some(filetime) = attrs.filetime {
find_data.file_time_lo = (filetime & 0xFFFFFFFF) as u32;
find_data.file_time_hi = (filetime >> 32) as u32;
}
}
}
}
ArchiveHandle::Mutable { archive, .. } => {
if let Some((_, Some(block_idx))) = file_entry.table_indices {
if let Some(attrs) = archive.archive().get_file_attributes(block_idx) {
if let Some(filetime) = attrs.filetime {
find_data.file_time_lo = (filetime & 0xFFFFFFFF) as u32;
find_data.file_time_hi = (filetime >> 32) as u32;
}
}
}
}
}
}
}
const ERROR_NO_MORE_FILES: u32 = 18;
#[no_mangle]
pub unsafe extern "C" fn SFileCreateArchive2(
filename: *const c_char,
create_info: *const SFILE_CREATE_MPQ,
handle: *mut HANDLE,
) -> bool {
if filename.is_null() || create_info.is_null() || handle.is_null() {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let info = &*create_info;
if info.cb_size != std::mem::size_of::<SFILE_CREATE_MPQ>() as u32 {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
let filename_str = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let version = match info.mpq_version {
1 => FormatVersion::V1,
2 => FormatVersion::V2,
3 => FormatVersion::V3,
4 => FormatVersion::V4,
_ => {
set_last_error(ERROR_INVALID_PARAMETER);
return false;
}
};
let _hash_table_size = if info.max_file_count > 0 {
info.max_file_count.max(4).next_power_of_two()
} else {
16
};
let sector_size = if info.sector_size > 0 && info.sector_size <= 23 {
info.sector_size as u16
} else {
3 };
let mut builder = ArchiveBuilder::new()
.version(version)
.block_size(sector_size);
if info.file_flags_1 != 0 {
builder = builder.listfile_option(ListfileOption::Generate);
} else {
builder = builder.listfile_option(ListfileOption::None);
}
if info.attr_flags != 0 {
if (info.attr_flags & MPQ_ATTRIBUTE_MD5) != 0 {
builder = builder.attributes_option(AttributesOption::GenerateFull);
} else if (info.attr_flags & MPQ_ATTRIBUTE_CRC32) != 0 {
builder = builder.attributes_option(AttributesOption::GenerateCrc32);
}
}
match builder.build(filename_str) {
Ok(_) => {
match MutableArchive::open(filename_str) {
Ok(mut_archive) => {
let mut next_id = NEXT_HANDLE.lock().unwrap();
let handle_id = *next_id;
*next_id += 1;
drop(next_id);
let archive_handle = ArchiveHandle::Mutable {
archive: mut_archive,
path: filename_str.to_string(),
};
ARCHIVES.lock().unwrap().insert(handle_id, archive_handle);
*handle = id_to_handle(handle_id);
set_last_error(ERROR_SUCCESS);
true
}
Err(_) => {
SFileOpenArchive(filename, 0, 0, handle)
}
}
}
Err(e) => {
let error_code = match e {
wow_mpq::Error::FileNotFound(_) => ERROR_FILE_NOT_FOUND,
wow_mpq::Error::InvalidFormat(_) => ERROR_INVALID_PARAMETER,
wow_mpq::Error::Io(_) => ERROR_ACCESS_DENIED,
_ => ERROR_ACCESS_DENIED,
};
set_last_error(error_code);
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handle_conversion() {
let id = 42usize;
let handle = id_to_handle(id);
assert_eq!(handle_to_id(handle), Some(id));
assert_eq!(handle_to_id(ptr::null_mut()), None);
}
#[test]
fn test_error_handling() {
set_last_error(ERROR_FILE_NOT_FOUND);
assert_eq!(SFileGetLastError(), ERROR_FILE_NOT_FOUND);
SFileSetLastError(ERROR_SUCCESS);
assert_eq!(SFileGetLastError(), ERROR_SUCCESS);
}
#[test]
fn test_locale() {
let old = SFileSetLocale(0x409); assert_eq!(old, LOCALE_NEUTRAL);
assert_eq!(SFileGetLocale(), 0x409);
SFileSetLocale(old); }
#[test]
fn test_extract_file_invalid_params() {
unsafe {
assert!(!SFileExtractFile(
ptr::null_mut(),
ptr::null(),
c"output.txt".as_ptr(),
0
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileExtractFile(
ptr::null_mut(),
c"test.txt".as_ptr(),
ptr::null(),
0
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileExtractFile(
ptr::null_mut(),
c"test.txt".as_ptr(),
c"output.txt".as_ptr(),
0
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_HANDLE);
}
}
#[test]
fn test_verify_file_invalid_params() {
unsafe {
assert!(!SFileVerifyFile(
ptr::null_mut(),
ptr::null(),
SFILE_VERIFY_ALL
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileVerifyFile(
ptr::null_mut(),
c"test.txt".as_ptr(),
SFILE_VERIFY_ALL
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_HANDLE);
}
}
#[test]
fn test_verify_archive_invalid_params() {
unsafe {
assert!(!SFileVerifyArchive(ptr::null_mut(), SFILE_VERIFY_SIGNATURE));
}
assert_eq!(SFileGetLastError(), ERROR_INVALID_HANDLE);
}
#[test]
fn test_create_archive_invalid_params() {
unsafe {
let mut handle = ptr::null_mut();
assert!(!SFileCreateArchive(
ptr::null(),
CREATE_ALWAYS,
16,
&mut handle
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileCreateArchive(
c"test.mpq".as_ptr(),
CREATE_ALWAYS,
16,
ptr::null_mut()
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileCreateArchive(
c"test.mpq".as_ptr(),
CREATE_ALWAYS,
15, &mut handle
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
assert!(!SFileCreateArchive(
c"test.mpq".as_ptr(),
CREATE_ALWAYS,
2, &mut handle
));
assert_eq!(SFileGetLastError(), ERROR_INVALID_PARAMETER);
}
}
}