use core::ffi::c_void;
use crate::abi::{ModHandle, ResourceKey, ResourcePatchFn, Status, Str};
use crate::test::ServedResource;
use crate::test::table::{found, put, text, with};
pub(crate) extern "system" fn serve_resource(
_me: ModHandle,
key: *const ResourceKey,
base: *const ResourceKey,
patch: ResourcePatchFn,
user: *mut c_void,
) -> Status {
let Some(key) = (unsafe { key.as_ref() }).copied() else {
return Status::BAD_ARGS;
};
let Some(base) = (unsafe { base.as_ref() }).copied() else {
return Status::BAD_ARGS;
};
with(Status::MISSING, |s| {
if s.served
.iter()
.any(|served| served.0.key == key && served.0.base != base)
{
return Status::REFUSED;
}
s.served.push((
ServedResource { key, base },
patch,
user.expose_provenance(),
));
Status::OK
})
}
pub(crate) extern "system" fn rebuild_resource(_me: ModHandle, key: *const ResourceKey) -> Status {
let Some(key) = (unsafe { key.as_ref() }).copied() else {
return Status::BAD_ARGS;
};
with(Status::MISSING, |s| {
if !s.served.iter().any(|served| served.0.key == key) {
return Status::BAD_ARGS;
}
s.rebuilds.push(key);
Status::OK
})
}
pub(crate) extern "system" fn recipe_rule(
_me: ModHandle,
kind: crate::abi::RecipeRuleKind,
name: Str,
disable: u32,
) -> Status {
let name = text(name);
with(Status::MISSING, |s| {
s.recipe_rules.retain(|(k, n)| !(*k == kind && *n == name));
if disable != 0 {
s.recipe_rules.push((kind, name));
}
Status::OK
})
}
pub(crate) extern "system" fn recipe_disabled(
_me: ModHandle,
recipe: u32,
out: *mut u32,
) -> Status {
let disabled = with(false, |s| s.host.disabled_recipes.contains(&recipe));
if !out.is_null() {
unsafe { out.write(u32::from(disabled)) };
}
Status::OK
}
pub(crate) extern "system" fn recipe_kind(
_me: ModHandle,
item: u32,
out: *mut crate::abi::RecipeKind,
) -> Status {
found(with(None, |s| s.host.recipe_kinds.get(&item).copied()), out)
}
pub(crate) extern "system" fn add_text(
_me: ModHandle,
id: u32,
language: u32,
value: Str,
) -> Status {
let value = text(value);
with(Status::MISSING, |s| {
s.texts.push((id, language, value));
Status::OK
})
}
pub(crate) extern "system" fn item_data_open(_me: ModHandle, id: Str, out: *mut u32) -> Status {
let id = text(id);
let full = if id.contains(':') {
id
} else {
format!("{}:{id}", crate::test::MOD_ID)
};
let kind = with(None, |s| {
let index = s
.item_kinds
.iter()
.position(|k| *k == full)
.unwrap_or_else(|| {
s.item_kinds.push(full.clone());
s.item_kinds.len() - 1
});
u32::try_from(index).ok()
});
found(kind, out)
}
pub(crate) extern "system" fn entity_data_open(
me: ModHandle,
id: Str,
_flags: crate::abi::EntityDataFlags,
out: *mut u32,
) -> Status {
item_data_open(me, id, out)
}
pub(crate) extern "system" fn item_data_get(
_me: ModHandle,
kind: u32,
pide: u32,
item: u32,
out: *mut u8,
capacity: u32,
len: *mut u32,
) -> Status {
let value = with(None, |s| {
s.item_values
.get(&(kind, pide))
.filter(|(held, _)| *held == item)
.map(|(_, bytes)| bytes.clone())
});
let Some(value) = value else {
return Status::MISSING;
};
let size = u32::try_from(value.len()).unwrap_or(u32::MAX);
if !len.is_null() {
unsafe { len.write(size) };
}
if size > capacity || out.is_null() {
return Status::BAD_ARGS;
}
unsafe { out.copy_from_nonoverlapping(value.as_ptr(), value.len()) };
Status::OK
}
pub(crate) extern "system" fn item_data_set(
_me: ModHandle,
kind: u32,
pide: u32,
item: u32,
data: *const u8,
len: u32,
) -> Status {
if pide == 0 {
return Status::BAD_ARGS;
}
let value = if len == 0 || data.is_null() {
Vec::new()
} else {
unsafe { core::slice::from_raw_parts(data, len as usize) }.to_vec()
};
with(Status::MISSING, |s| {
if value.is_empty() {
s.item_values.remove(&(kind, pide));
} else {
s.item_values.insert((kind, pide), (item, value));
}
Status::OK
})
}
pub(crate) extern "system" fn content_serve(
_me: ModHandle,
data: *const u8,
len: u32,
out: *mut [u32; 4],
) -> Status {
if data.is_null() || len == 0 {
return Status::BAD_ARGS;
}
let bytes = unsafe { core::slice::from_raw_parts(data, len as usize) }.to_vec();
let hash = [
len,
u32::try_from(bytes.len()).unwrap_or(0) ^ 0x5a5a,
bytes.iter().map(|&b| u32::from(b)).sum(),
1,
];
with(Status::MISSING, |s| {
s.content.insert(hash, bytes);
Status::OK
});
found(Some(hash), out)
}
pub(crate) extern "system" fn content_read(
_me: ModHandle,
hash: *const [u32; 4],
out: *mut u8,
capacity: u32,
len: *mut u32,
) -> Status {
let Some(hash) = (unsafe { hash.as_ref() }).copied() else {
return Status::BAD_ARGS;
};
let Some(data) = with(None, |s| {
s.content
.get(&hash)
.or_else(|| s.host.archive.get(&hash))
.cloned()
}) else {
return Status::MISSING;
};
let size = u32::try_from(data.len()).unwrap_or(u32::MAX);
if !len.is_null() {
unsafe { len.write(size) };
}
if size > capacity || out.is_null() {
return Status::BAD_ARGS;
}
unsafe { out.copy_from_nonoverlapping(data.as_ptr(), data.len()) };
Status::OK
}
pub(crate) extern "system" fn icons_ready(_me: ModHandle) -> Status {
if with(false, |s| s.host.icons_ready) {
Status::OK
} else {
Status::MISSING
}
}
pub(crate) extern "system" fn resource_read(
_me: ModHandle,
key: *const ResourceKey,
out: *mut u8,
capacity: u32,
len: *mut u32,
) -> Status {
let Some(key) = (unsafe { key.as_ref() }).copied() else {
return Status::BAD_ARGS;
};
let Some(data) = with(None, |s| {
s.host
.resources
.get(&(key.guid, key.type_hash, key.part))
.cloned()
}) else {
return Status::MISSING;
};
let size = u32::try_from(data.len()).unwrap_or(u32::MAX);
if !len.is_null() {
unsafe { len.write(size) };
}
if size > capacity || out.is_null() {
return Status::BAD_ARGS;
}
unsafe { out.copy_from_nonoverlapping(data.as_ptr(), data.len()) };
Status::OK
}
pub(crate) extern "system" fn game_text(
_me: ModHandle,
id: u32,
language: u32,
out: *mut Str,
) -> Status {
let english = crate::i18n::Language::EN_US.0;
let found = with(None, |s| {
let texts = &s.host.game_texts;
let language = if language == 0 { english } else { language };
texts
.get(&(id, language))
.or_else(|| texts.get(&(id, english)))
.copied()
});
match found {
Some(text) => put(out, Str::new(text)),
None => Status::MISSING,
}
}