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: crate::RecipeId,
out: *mut u32,
) -> Status {
let disabled = with(false, |s| s.host.disabled_recipes.contains(&recipe.get()));
if !out.is_null() {
unsafe { out.write(u32::from(disabled)) };
}
Status::OK
}
pub(crate) extern "system" fn recipe_kind(
_me: ModHandle,
item: crate::ItemId,
out: *mut crate::abi::RecipeKind,
) -> Status {
found(
with(None, |s| s.host.recipe_kinds.get(&item.get()).copied()),
out,
)
}
pub(crate) extern "system" fn change_data(
_me: ModHandle,
change: *const crate::abi::DataChange,
) -> Status {
let Some(raw) = (unsafe {
crate::abi::read_headed(change, core::mem::offset_of!(crate::abi::DataChange, item))
}) else {
return Status::BAD_ARGS;
};
let source = unsafe { raw.source.as_str() }
.unwrap_or_default()
.to_owned();
if raw.select != crate::abi::DataSelect::ENTRY {
if let Ok(Some(change)) = crate::content::changes::RecipesChange::from_abi(&raw) {
return with(Status::MISSING, |s| {
s.changes.recipes.push((change, source));
Status::OK
});
}
let Ok(Some(change)) = crate::content::changes::ItemsChange::from_abi(&raw) else {
return Status::BAD_ARGS;
};
return with(Status::MISSING, |s| {
s.changes.items.push((change, source));
Status::OK
});
}
let Ok(Some(change)) = crate::content::changes::Change::from_abi(&raw) else {
return Status::BAD_ARGS;
};
with(Status::MISSING, |s| {
s.changes.one.push((change, source));
Status::OK
})
}
pub(crate) extern "system" fn declare_change(
_me: ModHandle,
change: *const crate::abi::DataChange,
out: *mut crate::abi::ChangeId,
) -> Status {
use crate::content::changes::{Change, Field, ItemsChange, Target};
if out.is_null() {
return Status::BAD_ARGS;
}
let Some(raw) =
(unsafe { crate::abi::read_headed(change, size_of::<crate::abi::DataChange>()) })
else {
return Status::BAD_ARGS;
};
let target = if raw.select == crate::abi::DataSelect::ENTRY {
Change::from_abi(&raw)
.ok()
.flatten()
.map(|c| (Target::Entry(c.entry), c.field))
} else {
ItemsChange::from_abi(&raw)
.ok()
.flatten()
.map(|c| (Target::Items(c.items), c.field))
};
let Some((target, field)) = target.filter(|(_, field)| *field != Field::Ingredient) else {
return Status::BAD_ARGS;
};
let source = unsafe { raw.source.as_str() }
.unwrap_or_default()
.to_owned();
with(Status::MISSING, |s| {
s.changes.live.push(crate::test::content::LiveChange {
target,
field,
source,
value: None,
});
let id = crate::abi::ChangeId(u32::try_from(s.changes.live.len()).unwrap_or(u32::MAX));
unsafe { out.write(id) };
Status::OK
})
}
pub(crate) extern "system" fn set_change(
_me: ModHandle,
id: crate::abi::ChangeId,
op: crate::abi::ChangeOp,
value: f64,
) -> Status {
use crate::content::changes::Value;
let value = match op {
crate::abi::ChangeOp::SET => Some(Value::Set(value)),
crate::abi::ChangeOp::TIMES => Some(Value::Times(value)),
crate::abi::ChangeOp::RESET => None,
_ => return Status::BAD_ARGS,
};
with(Status::MISSING, |s| {
let at = usize::try_from(id.0).ok().and_then(|n| n.checked_sub(1));
let Some(live) = at.and_then(|at| s.changes.live.get_mut(at)) else {
return Status::BAD_ARGS;
};
if value.is_some_and(|value| live.field.check(value).is_err()) {
return Status::BAD_ARGS;
}
live.value = value;
Status::OK
})
}
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: crate::Entity,
item: crate::ItemId,
out: *mut u8,
capacity: u32,
len: *mut u32,
) -> Status {
let value = with(None, |s| {
s.item_values
.get(&(kind, pide.0))
.filter(|(held, _)| *held == item.get())
.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: crate::Entity,
item: crate::ItemId,
data: *const u8,
len: u32,
) -> Status {
if pide.is_none() {
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.host.players.iter().any(|p| p.entity == pide) {
return Status::NOT_KEPT;
}
if value.is_empty() {
s.item_values.remove(&(kind, pide.0));
} else {
s.item_values.insert((kind, pide.0), (item.get(), 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,
}
}