diff --git a/crates/smashline/src/lib.rs b/crates/smashline/src/lib.rs index 1a7fa4db..22996822 100644 --- a/crates/smashline/src/lib.rs +++ b/crates/smashline/src/lib.rs @@ -92,6 +92,12 @@ impl Costume { } } +#[repr(C)] +pub struct CloneWeaponInfo { + pub kind: i32, + pub table_id: i32, +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub enum Acmd { @@ -415,11 +421,11 @@ decl_imports! { fn smashline_clone_weapon( original_owner: StringFFI, - original_article_id: i32, + original_weapon_kind: i32, new_owner: StringFFI, new_name: StringFFI, use_original_code: bool - ) -> i32; + ) -> CloneWeaponInfo; fn smashline_update_weapon_count( article_id: i32, @@ -462,7 +468,7 @@ pub fn clone_weapon( new_owner: impl Into, new_name: impl Into, use_original_code: bool, -) -> i32 { +) -> CloneWeaponInfo { smashline_clone_weapon( StringFFI::from_str(original_owner), original_article_id, diff --git a/src/api.rs b/src/api.rs index b709ce30..19379763 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,17 +1,19 @@ use std::{ + ffi::CString, num::{NonZeroU64, NonZeroUsize}, ptr::NonNull, + sync::atomic::Ordering, }; use acmd_engine::action::ActionRegistry; use rtld::Section; use smashline::{ - Acmd, AcmdFunction, AgentEntry, Costume, Hash40, L2CAgentBase, ObjectEvent, Priority, StatusLine, StringFFI, + Acmd, AcmdFunction, AgentEntry, CloneWeaponInfo, Costume, Hash40, L2CAgentBase, ObjectEvent, Priority, StatusLine, StringFFI, }; use crate::{ callbacks::{StatusCallback, StatusCallbackFunction}, - cloning::weapons::{NewAgent, NewArticle}, + cloning::weapons::{NewWeapon, BASE_WEAPON_KIND, WEAPON_COUNT, WEAPON_KIND_HASHES, WEAPON_NAMES, WEAPON_OWNER_KINDS, WEAPON_OWNER_NAMES}, create_agent::{ AcmdScript, StatusScript, StatusScriptFunction, LOWERCASE_FIGHTER_NAMES, LOWERCASE_WEAPON_NAMES @@ -303,16 +305,16 @@ pub extern "C" fn smashline_reload_script( #[no_mangle] pub extern "C" fn smashline_clone_weapon( original_owner: StringFFI, - original_article_id: i32, + original_weapon_kind: i32, new_owner: StringFFI, new_name: StringFFI, use_original_code: bool, -) -> i32 { +) -> CloneWeaponInfo { let original_owner = original_owner.as_str().unwrap().to_string(); let new_owner = new_owner.as_str().unwrap().to_string(); let new_name = new_name.as_str().unwrap().to_string(); - let original_owner_id = LOWERCASE_FIGHTER_NAMES + let original_owner_kind = LOWERCASE_FIGHTER_NAMES .iter() .position(|name| name == original_owner) .unwrap(); @@ -322,60 +324,62 @@ pub extern "C" fn smashline_clone_weapon( // .position(|name| name == original_name) // .unwrap(); - let original_name = LOWERCASE_WEAPON_NAMES.get(original_article_id as usize).unwrap(); + let original_name = LOWERCASE_WEAPON_NAMES.get(original_weapon_kind as usize).unwrap(); - let new_owner_id = LOWERCASE_FIGHTER_NAMES + let new_owner_kind = LOWERCASE_FIGHTER_NAMES .iter() .position(|name| name == new_owner) .unwrap(); - let mut new_agents = crate::cloning::weapons::NEW_AGENTS.write(); + let mut new_weapons = crate::cloning::weapons::NEW_WEAPONS.write(); - let mut new_articles = crate::cloning::weapons::NEW_ARTICLES.write(); - let articles = new_articles - .entry(new_owner_id as i32) + let weapons = new_weapons + .entry(new_owner_kind as i32) .or_default(); - if let Some(id) = articles.iter().position(|article| - article.original_owner == original_owner_id as i32 && - article.weapon_id == original_article_id - ) { - return id as i32; - } - - for agents in new_agents.values() { - if let Some(agent) = agents.iter().find(|agent| - agent.owner_name == new_owner && agent.new_name == new_name - ) { - let owner = LOWERCASE_FIGHTER_NAMES.get(agent.old_owner_id as usize).unwrap(); - panic!( - "Weapon with the name '{}_{}' has already been cloned, but using '{}_{}' instead of '{}_{}'", - new_owner, new_name, owner, agent.old_name, original_owner, original_name - ); + for (i, weapon) in weapons.iter().enumerate() { + if weapon.old_owner_kind == original_owner_kind as i32 + && weapon.owner_name == new_owner + && weapon.new_name == new_name + && weapon.old_name == original_name + && weapon.old_kind == original_weapon_kind { + // TODO: Properly handle a situation where we're + // cloning a weapon, even though it already exists. + // Maybe only consider if new_name is the same? + + return CloneWeaponInfo { + kind: weapon.kind, + table_id: i as i32, + }; } } - new_agents - .entry(original_article_id as i32) - .or_default() - .push(NewAgent { - old_owner_id: original_owner_id as i32, - owner_id: new_owner_id as i32, - owner_name_ffi: format!("{new_owner}\0"), - new_name_ffi: format!("{new_name}\0"), - owner_name: new_owner, - new_name, - old_name: original_name.to_string(), - use_original_code, - }); - - let id = articles.len(); - articles.push(NewArticle { - original_owner: original_owner_id as i32, - weapon_id: original_article_id, + let kind = WEAPON_COUNT.fetch_add(1, Ordering::Relaxed) as i32; + let table_id = weapons.len(); + + weapons.push(NewWeapon { + old_owner_kind: original_owner_kind as i32, + owner_kind: new_owner_kind as i32, + owner_name: new_owner.clone(), + new_name: new_name.clone(), + old_name: original_name.to_string(), + kind, + old_kind: original_weapon_kind as i32, + use_original_code, }); - id as i32 + WEAPON_NAMES.write().push(CString::new(new_name.clone()).unwrap().into_raw()); + WEAPON_OWNER_NAMES.write().push(CString::new(new_owner.clone()).unwrap().into_raw()); + WEAPON_OWNER_KINDS.write().push(new_owner_kind as i32); + WEAPON_KIND_HASHES.write().push(Hash40::new( + &format!("weapon_kind_{}_{}", new_owner, new_name) + ).0); + BASE_WEAPON_KIND.write().push(original_weapon_kind as i32); + + CloneWeaponInfo { + kind, + table_id: table_id as i32, + } } #[no_mangle] diff --git a/src/cloning/weapons.rs b/src/cloning/weapons.rs index 7f7b8c5c..7e80358a 100644 --- a/src/cloning/weapons.rs +++ b/src/cloning/weapons.rs @@ -1,42 +1,41 @@ use std::{ collections::BTreeMap, - sync::atomic::{AtomicBool, AtomicI32, Ordering}, + sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}, + ffi::{c_char, c_void}, }; use locks::RwLock; use skyline::hooks::InlineCtx; use smashline::{skyline_smash::app::BattleObjectModuleAccessor, Hash40}; -pub struct NewAgent { - pub old_owner_id: i32, - pub owner_id: i32, - pub owner_name_ffi: String, - pub new_name_ffi: String, +use crate::dynamic_accessor::DynamicArrayAccessor; + +pub struct NewWeapon { + pub old_owner_kind: i32, + pub owner_kind: i32, pub owner_name: String, pub new_name: String, pub old_name: String, + pub kind: i32, + pub old_kind: i32, pub use_original_code: bool, } -pub struct NewArticle { - pub original_owner: i32, - pub weapon_id: i32, -} +pub static NEW_WEAPONS: RwLock>> = RwLock::new(BTreeMap::new()); +pub static IS_USING_ORIGINAL_CODE: AtomicBool = AtomicBool::new(false); -pub static NEW_ARTICLES: RwLock>> = RwLock::new(BTreeMap::new()); -pub static NEW_AGENTS: RwLock>> = RwLock::new(BTreeMap::new()); -pub static IGNORE_NEW_AGENTS: AtomicBool = AtomicBool::new(false); +pub const ORIGINAL_WEAPON_COUNT: usize = 0x267; +pub static WEAPON_COUNT: AtomicUsize = AtomicUsize::new(ORIGINAL_WEAPON_COUNT); -pub static WEAPON_COUNT_UPDATE: RwLock> = RwLock::new(BTreeMap::new()); +pub static WEAPON_NAMES: RwLock> = RwLock::new(DynamicArrayAccessor::new(0x5185bd0, ORIGINAL_WEAPON_COUNT)); +pub static WEAPON_OWNER_NAMES: RwLock> = RwLock::new(DynamicArrayAccessor::new(0x5188240, ORIGINAL_WEAPON_COUNT)); +pub static WEAPON_OWNER_KINDS: RwLock> = RwLock::new(DynamicArrayAccessor::new(0x455d7e4, ORIGINAL_WEAPON_COUNT)); +pub static WEAPON_KIND_HASHES: RwLock> = RwLock::new(DynamicArrayAccessor::new(0x455e650, ORIGINAL_WEAPON_COUNT)); +pub static BASE_WEAPON_KIND: RwLock> = RwLock::new(Vec::new()); -pub fn try_get_new_agent( - map: &BTreeMap>, - weapon: i32, - owner: i32, -) -> Option<&NewAgent> { - map.get(&weapon) - .and_then(|v| v.iter().find(|a| a.owner_id == owner)) -} +pub static CURRENT_WEAPON_KIND: AtomicI32 = AtomicI32::new(-1); + +pub static WEAPON_COUNT_UPDATE: RwLock> = RwLock::new(BTreeMap::new()); pub static CURRENT_OWNER_KIND: AtomicI32 = AtomicI32::new(-1); @@ -141,28 +140,29 @@ fn get_static_fighter_data(kind: i32) -> *const StaticFighterData { new_descriptors.extend_from_slice(unsafe { (*original_data).articles_as_slice() }); - for article in new_descriptors.iter_mut() { - let weapon_count = WEAPON_COUNT_UPDATE.read(); - if let Some(new_count) = weapon_count.get(&article.weapon_id) { - article.max_count = *new_count; - } - } - - if let Some(new_articles) = NEW_ARTICLES.read().get(&kind) { + if let Some(new_weapons) = NEW_WEAPONS.read().get(&kind) { - for article in new_articles.iter() { - let source_data = call_original!(article.original_owner); + for weapon in new_weapons.iter() { + let source_data = call_original!(weapon.old_owner_kind); unsafe { - let Some(article) = (*source_data).get_article(article.weapon_id) else { + let Some(mut article) = (*source_data).get_article(weapon.old_kind) else { panic!("Failed to append article table"); }; + article.weapon_id = weapon.kind; new_descriptors.push(article); } } } + for article in new_descriptors.iter_mut() { + let weapon_count = WEAPON_COUNT_UPDATE.read(); + if let Some(new_count) = weapon_count.get(&article.weapon_id) { + article.max_count = *new_count; + } + } + let count = new_descriptors.len(); let ptr = new_descriptors.leak().as_ptr(); let static_article_info = Box::leak(Box::new(StaticArticleData { @@ -175,60 +175,42 @@ fn get_static_fighter_data(kind: i32) -> *const StaticFighterData { Box::leak(new_fighter_data) } -fn weapon_owner_hook(ctx: &mut InlineCtx, source_register: usize, dst_register: usize) { - if IGNORE_NEW_AGENTS.load(Ordering::Relaxed) { - return; - } - - let owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - let Some(agent) = try_get_new_agent(&agents, unsafe { ctx.registers[source_register].x() as i32 }, owner) else { - return; - }; +fn weapon_owner_hook(ctx: &mut InlineCtx, source_register: usize, shift: u32, dst_register: usize) { + let mut kind = ctx.registers[source_register].x() >> shift; - unsafe { - ctx.registers[dst_register].set_x(agent.owner_id as u64); + if IS_USING_ORIGINAL_CODE.load(Ordering::Relaxed) { + kind = BASE_WEAPON_KIND.read()[(kind as usize) - crate::cloning::weapons::ORIGINAL_WEAPON_COUNT] as u64; } -} -fn weapon_owner_name_hook(ctx: &mut InlineCtx, source_register: usize, dst_register: usize) { - if IGNORE_NEW_AGENTS.load(Ordering::Relaxed) { - return; - } + ctx.registers[dst_register].set_x(WEAPON_OWNER_KINDS.read()[kind as usize] as u64); +} - let owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - let Some(agent) = try_get_new_agent(&agents, unsafe { ctx.registers[source_register].x() as i32 }, owner) else { - return; - }; +fn weapon_owner_name_hook(ctx: &mut InlineCtx, source_register: usize, shift: u32, dst_register: usize) { + let mut kind = ctx.registers[source_register].x() >> shift; - unsafe { - ctx.registers[dst_register].set_x(agent.owner_name_ffi.as_ptr() as u64); + if IS_USING_ORIGINAL_CODE.load(Ordering::Relaxed) { + kind = BASE_WEAPON_KIND.read()[(kind as usize) - crate::cloning::weapons::ORIGINAL_WEAPON_COUNT] as u64; } + + ctx.registers[dst_register].set_x(WEAPON_OWNER_NAMES.read()[kind as usize] as u64); } -fn weapon_name_hook(ctx: &mut InlineCtx, source_register: usize, dst_register: usize) { - if IGNORE_NEW_AGENTS.load(Ordering::Relaxed) { - return; - } +fn weapon_name_hook(ctx: &mut InlineCtx, source_register: usize, shift: u32, dst_register: usize) { + let mut kind = ctx.registers[source_register].x() >> shift; - let owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - let Some(agent) = try_get_new_agent(&agents, unsafe { ctx.registers[source_register].x() as i32 }, owner) else { - return; - }; - - unsafe { - ctx.registers[dst_register].set_x(agent.new_name_ffi.as_ptr() as u64); + if IS_USING_ORIGINAL_CODE.load(Ordering::Relaxed) { + kind = BASE_WEAPON_KIND.read()[(kind as usize) - crate::cloning::weapons::ORIGINAL_WEAPON_COUNT] as u64; } + + ctx.registers[dst_register].set_x(WEAPON_NAMES.read()[kind as usize] as u64); } macro_rules! decl_hooks { - ($install_fn:ident => $func:expr; $($name:ident($src:expr, $dst:expr, $offset:expr));*) => { + ($install_fn:ident => $func:expr; $($name:ident($src:expr, $shift:expr, $dst:expr, $offset:expr));*) => { $( #[skyline::hook(offset = $offset, inline)] unsafe fn $name(ctx: &mut InlineCtx) { - $func(ctx, $src, $dst); + $func(ctx, $src, $shift, $dst); } )* @@ -244,32 +226,32 @@ macro_rules! decl_hooks { decl_hooks! { install_weapon_owner_hooks => weapon_owner_hook; - params(21, 26, 0x33b6628); - game_animcmd_owner(22, 8, 0x33acf78); - sound_animcmd_owner(22, 8, 0x33aee38); - effect_animcmd_owner(22, 8, 0x33aded8); - status_script_owner(22, 8, 0x33ac040) + params(21, 0, 26, 0x33b6628); + game_animcmd_owner(22, 0, 8, 0x33acf78); + sound_animcmd_owner(22, 0, 8, 0x33aee38); + effect_animcmd_owner(22, 0, 8, 0x33aded8); + status_script_owner(22, 0, 8, 0x33ac040) } decl_hooks! { install_weapon_owner_name_hooks => weapon_owner_name_hook; - get_file(26, 25, 0x17e0a4c); - game_animcmd_owner_name(8, 2, 0x33ace7c); - sound_animcmd_owner_name(8, 2, 0x33aed3c); - effect_animcmd_owner_name(8, 2, 0x33adddc); - status_script_owner_name(8, 2, 0x33abf54) + get_file(26, 0, 25, 0x17e0a4c); + game_animcmd_owner_name(8, 3, 2, 0x33ace7c); + sound_animcmd_owner_name(8, 3, 2, 0x33aed3c); + effect_animcmd_owner_name(8, 3, 2, 0x33adddc); + status_script_owner_name(8, 3, 2, 0x33abf54) } decl_hooks! { install_weapon_name_hooks => weapon_name_hook; - get_file_weapon_name(23, 22, 0x17e098c); - normal_param_data(21, 27, 0x33b6830); - map_collision_param_data(21, 2, 0x33b69f0); - visibility_param_data(21, 2, 0x33b6d14); - game_animcmd_weapon_name(8, 3, 0x33ace8c); - sound_animcmd_weapon_name(8, 3, 0x33aed4c); - effect_animcmd_weapon_name(8, 3, 0x33addec); - status_script_weapon_name(8, 3, 0x33abf64) + get_file_weapon_name(23, 0, 22, 0x17e0890); + normal_param_data(21, 0, 27, 0x33b6830); + map_collision_param_data(21, 0, 2, 0x33b69f0); + visibility_param_data(21, 0, 2, 0x33b6d14); + game_animcmd_weapon_name(8, 3, 3, 0x33ace8c); + sound_animcmd_weapon_name(8, 3, 3, 0x33aed4c); + effect_animcmd_weapon_name(8, 3, 3, 0x33addec); + status_script_weapon_name(8, 3, 3, 0x33abf64) } macro_rules! decl_hooks_kirby_get_kind { @@ -374,13 +356,128 @@ unsafe fn kirby_get_copy_articles(ctx: &mut InlineCtx, store_reg: usize) { ctx.registers[store_reg].set_x(static_article_info as *const StaticArticleData as u64); } +macro_rules! decl_hooks_mimic_echo_weapon { + ($install_fn:ident; $($name:ident($offset:expr) -> $return_type:ty);*) => { + $( + #[skyline::hook(offset = $offset)] + unsafe fn $name(kind: i32) -> $return_type { + let k = if kind < ORIGINAL_WEAPON_COUNT as i32 + || kind >= WEAPON_COUNT.load(Ordering::Relaxed) as i32 { + kind + } else { + BASE_WEAPON_KIND.read()[(kind as usize) - ORIGINAL_WEAPON_COUNT] + }; + + call_original!(k) + } + )* + + fn $install_fn() { + skyline::install_hooks!( + $( + $name, + )* + ); + } + }; + (hashes; $install_fn:ident; $($name:ident($offset:expr));*) => { + $( + #[skyline::hook(offset = $offset, inline)] + unsafe fn $name(ctx: &mut InlineCtx) { + let kind = ctx.registers[20].w() as i32; + + let hash = if kind > WEAPON_COUNT.load(Ordering::Relaxed) as i32 { + Hash40::new("weapon_kind_none").0 + } else { + WEAPON_KIND_HASHES.read()[kind as usize] + }; + + ctx.registers[4].set_x(hash); + } + )* + + fn $install_fn() { + skyline::install_hooks!( + $( + $name, + )* + ); + } + }; +} + +decl_hooks_mimic_echo_weapon! { + install_mimic_echo_weapon_hooks; + get_weapon_bone_stuff(0x33aa1e0) -> *const c_void; + get_weapon_vtable(0x33be790) -> *const c_void; + idk(0x33afc00) -> i32 +} + +decl_hooks_mimic_echo_weapon! { + hashes; + install_mimic_echo_weapon_kind_hash_hooks; + get_hashes1(0x3ae24c); + get_hashes2(0x3ae7bc) +} + +#[skyline::hook(offset = 0x17e09a8, inline)] +unsafe fn mimic_echo_weapon_file_category(ctx: &mut InlineCtx) { + let mut kind = ctx.registers[26].x() as i32; + + if kind >= ORIGINAL_WEAPON_COUNT as i32 { + kind = BASE_WEAPON_KIND.read()[(kind as usize) - ORIGINAL_WEAPON_COUNT]; + } + + // Just in case kind is still somehow larger + // than the original weapon count, then we're + // going to assume "fighter" when getting the file. + // There's only 1 case where it's "enemy" instead + if kind >= ORIGINAL_WEAPON_COUNT as i32 { + kind = 0; + } + + let text = skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as *const u8; + let file_category_table = text.add(0x5186f08) as *const *const c_char; + let file_category = *file_category_table.add(kind as usize); + + ctx.registers[25].set_x(file_category as u64); +} + +#[skyline::hook(offset = 0x33b5d10, inline)] +unsafe fn save_weapon_kind(ctx: &mut InlineCtx) { + let mut kind = ctx.registers[28].w(); + + CURRENT_WEAPON_KIND.store(kind as i32, Ordering::Relaxed); + + if kind >= ORIGINAL_WEAPON_COUNT as u32 { + kind = BASE_WEAPON_KIND.read()[(kind as usize) - ORIGINAL_WEAPON_COUNT] as u32; + } + + ctx.registers[28].set_w(kind); +} + +#[skyline::hook(offset = 0x33b6528, inline)] +unsafe fn restore_weapon_kind(ctx: &mut InlineCtx) { + ctx.registers[28].set_w(CURRENT_WEAPON_KIND.load(Ordering::Relaxed) as u32); +} + pub fn install() { + skyline::patching::Patch::in_text(0x3ae23c).nop().unwrap(); + skyline::patching::Patch::in_text(0x3ae7ac).nop().unwrap(); + skyline::patching::Patch::in_text(0x17e0894).nop().unwrap(); + install_weapon_name_hooks(); install_weapon_owner_hooks(); install_weapon_owner_name_hooks(); + install_mimic_echo_weapon_hooks(); + install_mimic_echo_weapon_kind_hash_hooks(); + skyline::install_hooks!( - get_static_fighter_data + get_static_fighter_data, + mimic_echo_weapon_file_category, + save_weapon_kind, + restore_weapon_kind, ); install_kirby_copy_kind_hooks(); diff --git a/src/create_agent.rs b/src/create_agent.rs index 56a44ce1..e12ee3ae 100644 --- a/src/create_agent.rs +++ b/src/create_agent.rs @@ -2,7 +2,9 @@ use std::{ borrow::BorrowMut, collections::{BTreeMap, HashMap}, ops::{Deref, DerefMut}, sync::{ atomic::{AtomicBool, Ordering}, Arc, - }, time::Duration + }, + time::Duration, + ffi::CStr, }; use acmd_engine::SmashlineScript; @@ -23,7 +25,7 @@ use smashline::{ use vtables::{CustomDataAccessError, VirtualClass}; use crate::{ - cloning::weapons::IGNORE_NEW_AGENTS, interpreter::LoadedScript, + cloning::weapons::{IS_USING_ORIGINAL_CODE, WEAPON_NAMES, WEAPON_OWNER_NAMES}, interpreter::LoadedScript, static_accessor::StaticArrayAccessor, callbacks::{CALLBACKS, StatusCallbackFunction} }; @@ -565,16 +567,6 @@ fn create_agent_hook( Some(agent) } BattleObjectCategory::Weapon => { - let Some(name) = crate::utils::get_weapon_name(object.kind) else { - // TODO: Warn - return original.call(object, boma, lua_state); - }; - - let Some(owner) = crate::utils::get_weapon_owner_name(object.kind) else { - // TODO: Warn - return original.call(object, boma, lua_state); - }; - let (agent, additional_module) = if let Some(agent) = original.call(object, boma, lua_state) { @@ -585,9 +577,9 @@ fn create_agent_hook( std::thread::sleep(Duration::from_millis(1)); } - IGNORE_NEW_AGENTS.store(true, Ordering::Relaxed); + IS_USING_ORIGINAL_CODE.store(true, Ordering::Relaxed); let result = original.call(object, boma, lua_state); - IGNORE_NEW_AGENTS.store(false, Ordering::Relaxed); + IS_USING_ORIGINAL_CODE.store(false, Ordering::Relaxed); if let Some(agent) = result { (agent, Some(fighter_id)) @@ -606,6 +598,9 @@ fn create_agent_hook( } }; + let name = unsafe { CStr::from_ptr(WEAPON_NAMES.read()[object.kind as usize]).to_str().unwrap() }; + let owner = unsafe { CStr::from_ptr(WEAPON_OWNER_NAMES.read()[object.kind as usize]).to_str().unwrap() }; + let qualified_name = format!("{owner}_{name}"); let hash = Hash40::new(&qualified_name); @@ -1065,14 +1060,6 @@ fn create_agent_status_weapon( boma: &mut BattleObjectModuleAccessor, lua_state: *mut lua_State, ) -> Option<&'static mut L2CFighterBase> { - let Some(name) = crate::utils::get_weapon_name(object.kind) else { - return call_original!(object, boma, lua_state); - }; - - let Some(owner_name) = crate::utils::get_weapon_owner_name(object.kind) else { - return call_original!(object, boma, lua_state); - }; - let (is_new, agent, additional_fighter) = if let Some(agent) = call_original!(object, boma, lua_state) { (false, agent, None) @@ -1082,9 +1069,9 @@ fn create_agent_status_weapon( std::thread::sleep(Duration::from_millis(1)); } - IGNORE_NEW_AGENTS.store(true, Ordering::Relaxed); + IS_USING_ORIGINAL_CODE.store(true, Ordering::Relaxed); let result = call_original!(object, boma, lua_state); - IGNORE_NEW_AGENTS.store(false, Ordering::Relaxed); + IS_USING_ORIGINAL_CODE.store(false, Ordering::Relaxed); if let Some(agent) = result { (false, agent, Some(fighter_id)) @@ -1123,6 +1110,9 @@ fn create_agent_status_weapon( .vtable_accessor_mut() .set_set_status_scripts(set_status_scripts); + let name = unsafe { CStr::from_ptr(WEAPON_NAMES.read()[object.kind as usize]).to_str().unwrap() }; + let owner_name = unsafe { CStr::from_ptr(WEAPON_OWNER_NAMES.read()[object.kind as usize]).to_str().unwrap() }; + let data = vtables::vtable_custom_data_mut::<_, L2CFighterWrapper>(wrapper.deref_mut()); data.hash = Hash40::new(&format!("{owner_name}_{name}")); data.kind = object.kind; diff --git a/src/dynamic_accessor.rs b/src/dynamic_accessor.rs new file mode 100644 index 00000000..48c45136 --- /dev/null +++ b/src/dynamic_accessor.rs @@ -0,0 +1,84 @@ +use std::{ + ffi::{c_char, CStr, CString}, + ops::{Deref, DerefMut}, + sync::OnceLock, +}; + +pub trait PushArray { + fn push(&mut self, item: T); +} + +pub struct DynamicArrayAccessor { + data: OnceLock>, + offset: usize, + count: usize, +} + +impl DynamicArrayAccessor { + pub const fn new(offset: usize, count: usize) -> Self { + Self { + data: OnceLock::new(), + offset, + count, + } + } + + fn init_data(&self) -> Vec { + unsafe { + let text = skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as *const u8; + let array = text.add(self.offset) as *const T; + let s: &[T]= std::slice::from_raw_parts(array, self.count); + s.to_vec() + } + } +} + +impl Deref for DynamicArrayAccessor { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + self.data.get_or_init(|| self.init_data()) + } +} + +impl DerefMut for DynamicArrayAccessor { + fn deref_mut(&mut self) -> &mut Self::Target { + self.data.get_or_init(|| self.init_data()); + self.data.get_mut().unwrap() + } +} + +// 2+ weapons with the same name use the same pointer to that string. +// So, if someone is adding a new weapon with the same name, +// it'll also use the same pointer that the previous weapons use. +impl PushArray<*const c_char> for DynamicArrayAccessor<*const c_char> { + fn push(&mut self, item: *const c_char) { + let data: &mut Vec<*const c_char> = self; + + let new_item = unsafe { + data.iter() + .find(|&&x| CStr::from_ptr(x) == CStr::from_ptr(item)) + .inspect(|x| { + let _ = CString::from_raw(item as *mut i8); + }) + .unwrap_or(&item) + }; + + data.push(*new_item); + } +} + +macro_rules! primitives { + ($($T:ty)*) => { + $( + impl PushArray<$T> for DynamicArrayAccessor<$T> { + fn push(&mut self, item: $T) { + let data: &mut Vec<$T> = self; + data.push(item); + } + } + )* + } +} + +primitives![i8 u8 i16 u16 i32 u32 i64 u64 f32 f64]; diff --git a/src/lib.rs b/src/lib.rs index d85aa01e..6048de60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,7 @@ mod params; mod runtime_reload; mod state_callback; mod static_accessor; +mod dynamic_accessor; mod unwind; mod utils; diff --git a/src/params.rs b/src/params.rs index bbb0c72f..9e3dfa0c 100644 --- a/src/params.rs +++ b/src/params.rs @@ -15,7 +15,7 @@ use vtables::{vtable, VirtualClass}; use std::ops::{Deref, DerefMut}; -use crate::cloning::weapons::{try_get_new_agent, NEW_AGENTS, NEW_ARTICLES}; +use crate::cloning::weapons::{NEW_WEAPONS}; pub static WHITELISTED_PARAMS: RwLock>> = RwLock::new(BTreeMap::new()); @@ -323,10 +323,10 @@ unsafe fn init_fighter_p_object(ctx: &InlineCtx) { } let whitelisted_params = WHITELISTED_PARAMS.read(); - let new_articles = NEW_ARTICLES.read(); + let new_weapons = NEW_WEAPONS.read(); if whitelisted_params.get(&fighter_id).is_none() - && new_articles.get(&fighter_id).is_none() { + && new_weapons.get(&fighter_id).is_none() { return; } @@ -360,16 +360,13 @@ unsafe fn init_fighter_p_object(ctx: &InlineCtx) { let mut allowed_names = vec![]; let mut remap_names = HashMap::new(); - let new_agents = NEW_AGENTS.read(); - if let Some(articles) = new_articles.get(&fighter_id) { - for article in articles.iter() { - if let Some(agent) = try_get_new_agent(&new_agents, article.weapon_id, fighter_id) { - allowed_names.push(Hash40::new(&format!("param_{}", agent.new_name)).0); - remap_names.insert( - Hash40::new(&format!("param_{}", agent.old_name)).0, - Hash40::new(&format!("param_{}", agent.new_name)).0, - ); - } + if let Some(weapons) = new_weapons.get(&fighter_id) { + for weapon in weapons.iter() { + allowed_names.push(Hash40::new(&format!("param_{}", weapon.new_name)).0); + remap_names.insert( + Hash40::new(&format!("param_{}", weapon.old_name)).0, + Hash40::new(&format!("param_{}", weapon.new_name)).0, + ); } } diff --git a/src/utils.rs b/src/utils.rs index 670623cf..0faede18 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,36 +5,19 @@ use smashline::{Costume, Hash40}; use crate::{ cloning::fighters::CURRENT_PLAYER_ID, - cloning::weapons::{try_get_new_agent, CURRENT_OWNER_KIND, NEW_AGENTS}, + cloning::weapons::{CURRENT_OWNER_KIND, NEW_WEAPONS}, create_agent::{COSTUMES, LOWERCASE_WEAPON_NAMES, LOWERCASE_WEAPON_OWNER_NAMES, LOWERCASE_FIGHTER_NAMES} }; -pub fn get_weapon_name(id: i32) -> Option { +pub fn get_weapon_code_dependency(kind: i32) -> Option { let current_owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - if let Some(name) = try_get_new_agent(&agents, id, current_owner).map(|agent| agent.new_name.clone()) { - Some(name) - } else { - LOWERCASE_WEAPON_NAMES.get(id as usize).map(|x| x.to_string()) - } -} - -pub fn get_weapon_owner_name(id: i32) -> Option { - let current_owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - - if let Some(name) = try_get_new_agent(&agents, id, current_owner).map(|agent| agent.owner_name.clone()) { - Some(name) - } else { - LOWERCASE_WEAPON_OWNER_NAMES.get(id as usize).map(|x| x.to_string()) - } -} - -pub fn get_weapon_code_dependency(id: i32) -> Option { - let current_owner = CURRENT_OWNER_KIND.load(Ordering::Relaxed); - let agents = NEW_AGENTS.read(); - try_get_new_agent(&agents, id, current_owner).and_then(|x| x.use_original_code.then_some(x.old_owner_id)) + NEW_WEAPONS.read() + .get(¤t_owner) + .into_iter() + .flatten() + .find(|&x| x.kind == kind && x.use_original_code) + .map(|x| x.old_owner_kind) } pub fn get_costume_from_entry_id(entry_id: i32) -> Option {