From c748b144cad3248a0fd1525b92e31bd494bf2e20 Mon Sep 17 00:00:00 2001 From: Nobody Date: Mon, 6 Mar 2023 03:45:35 +0500 Subject: [PATCH] Fix data loading errors with 3.4+ excels --- Cargo.toml | 1 + excel-hash-wrapper-macro/.gitignore | 14 ++++ excel-hash-wrapper-macro/Cargo.toml | 16 +++++ excel-hash-wrapper-macro/src/lib.rs | 103 ++++++++++++++++++++++++++++ src/jsonmanager/avatar.rs | 63 ++++++++++++----- src/jsonmanager/avatar_skill.rs | 2 +- src/jsonmanager/monster.rs | 4 +- 7 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 excel-hash-wrapper-macro/.gitignore create mode 100644 excel-hash-wrapper-macro/Cargo.toml create mode 100644 excel-hash-wrapper-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 1d83c8a..3ed6364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ lua_serde = { path = "lua_serde" } packet-processor-macro = { path = "packet-processor-macro" } packet-processor = { path = "packet-processor" } rs-ipc = { path = "rs-ipc" } +excel-hash-wrapper-macro = { path = "excel-hash-wrapper-macro" } prost = "0.8" bytes = "1.1.0" diff --git a/excel-hash-wrapper-macro/.gitignore b/excel-hash-wrapper-macro/.gitignore new file mode 100644 index 0000000..4685c27 --- /dev/null +++ b/excel-hash-wrapper-macro/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Vim temp files +.*.swp +.*.swo diff --git a/excel-hash-wrapper-macro/Cargo.toml b/excel-hash-wrapper-macro/Cargo.toml new file mode 100644 index 0000000..cf3c291 --- /dev/null +++ b/excel-hash-wrapper-macro/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "excel-hash-wrapper-macro" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +regex = "1" +syn = { version = "1.0", features = ["full","derive"] } +quote = "1.0" +proc-macro2 = "1.0" +convert_case = "0.4.0" diff --git a/excel-hash-wrapper-macro/src/lib.rs b/excel-hash-wrapper-macro/src/lib.rs new file mode 100644 index 0000000..7d40c4b --- /dev/null +++ b/excel-hash-wrapper-macro/src/lib.rs @@ -0,0 +1,103 @@ +use proc_macro::{self, TokenStream}; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs}; + +fn get_nested_meta_name(nested_meta: &syn::NestedMeta) -> String { + match nested_meta { + syn::NestedMeta::Meta(meta) => match meta { + syn::Meta::Path(ident) => ident.get_ident().cloned().unwrap().to_string(), + _ => panic!("Unsupported macro argument"), + }, + syn::NestedMeta::Lit(_) => panic!("Only identifiers are supported as an arguments to the macro"), + } +} + +#[proc_macro_attribute] +pub fn excel_hash_wrapper(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as AttributeArgs); + let args = args.clone().into_iter().map(|a| get_nested_meta_name(&a)); + + // Generate all "X_hash", "X_hash_pre" and "X_hash_suffix" strings as well as getter names + let args_hash = args.clone().into_iter().map(|a| format!("{}_hash", a)); + let args_hash_pre = args.clone().into_iter().map(|a| format!("{}_hash_pre", a)); + let args_hash_suffix = args.clone().into_iter().map(|a| format!("{}_hash_suffix", a)); + let get_args_hash = args.clone().into_iter().map(|a| format!("get_{}_hash", a)); + + let args_hash: Vec = args_hash.map(|a| a.parse().unwrap()).collect(); + let args_hash_pre: Vec = args_hash_pre.map(|a| a.parse().unwrap()).collect(); + let args_hash_suffix: Vec = args_hash_suffix.map(|a| a.parse().unwrap()).collect(); + let get_args_hash: Vec = get_args_hash.map(|a| a.parse().unwrap()).collect(); + + let mut found_struct = false; + let mut struct_name = None; + + let modified_struct: proc_macro::TokenStream = input.into_iter().map(|r| { + match &r { + &proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs + found_struct = true; + r + }, + &proc_macro::TokenTree::Ident(ref ident) if found_struct == true && struct_name == None => { // Next ident right after "struct" is the struct name + struct_name = Some(ident.to_string()); + r + }, + &proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct + let mut stream = proc_macro::TokenStream::new(); + + stream.extend( + // For each hash name, generate three fields: prefix, suffix and the one that combines them + vec![proc_macro::TokenStream::from(quote!( + #( + pub #args_hash_pre: Option, + pub #args_hash_suffix: Option, + pub #args_hash: Option, + )* + ))] + ); + stream.extend(group.stream()); + + proc_macro::TokenTree::Group( + proc_macro::Group::new( + proc_macro::Delimiter::Brace, + stream + ) + ) + } + _ => r + } + }).collect(); + + let struct_name: proc_macro2::TokenStream = match &struct_name { + None => panic!("Failed to find struct name"), + Some(name) => name.clone().parse().unwrap(), + }; + + let implementation = vec![proc_macro::TokenStream::from(quote!( + // This implements getters for hashes + // They first try to extract the value from u64 field + // If it fails, they default to combining prefix and suffix + impl #struct_name { + #( + pub fn #get_args_hash (&self) -> u64 { + match self.#args_hash { + Some(value) => value, + None => match self.#args_hash_pre { + None => panic!("Attempt to get an empty prefix for {} (object {:?})", stringify!(#args_hash), self), + Some(prefix) => match self.#args_hash_suffix { + None => panic!("Attempt to get an empty suffix for {} (object {:?})", stringify!(#args_hash), self), + Some(suffix) => IdManager::get_hash_by_prefix_suffix(prefix, suffix), + } + } + } + } + )* + } + + ))]; + + let mut ret = proc_macro::TokenStream::new(); + ret.extend(modified_struct); + ret.extend(implementation); + println!("{}", ret); + ret +} \ No newline at end of file diff --git a/src/jsonmanager/avatar.rs b/src/jsonmanager/avatar.rs index afa925f..2f265bc 100644 --- a/src/jsonmanager/avatar.rs +++ b/src/jsonmanager/avatar.rs @@ -1,30 +1,70 @@ use serde::{Serialize, Deserialize}; +use crate::utils::IdManager; + +use excel_hash_wrapper_macro::*; + +#[excel_hash_wrapper(gacha_card_name, gacha_image_name)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all="PascalCase")] -#[derive(Serialize, Deserialize, Clone)] pub struct AvatarGachaHashes { - pub gacha_card_name_hash_pre: u8, + /*pub gacha_card_name_hash_pre: u8, pub gacha_card_name_hash_suffix: u32, pub gacha_image_name_hash_pre: u8, - pub gacha_image_name_hash_suffix: u32, + pub gacha_image_name_hash_suffix: u32,*/ } +#[excel_hash_wrapper( + prefab_path, + prefab_path_remote, + controller_path, + controller_path_remote, + prefab_path_ragdoll, + script_data_path, + combat_config, + manekin_path, + coop_pic_name, + manekin_json_config +)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all="PascalCase")] -#[derive(Serialize, Deserialize, Clone)] pub struct Avatar { // Entity fields pub id: u32, pub name_text_map_hash: u32, +/* pub prefab_path_hash_pre: u8, pub prefab_path_hash_suffix: u32, + pub prefab_path_remote_hash_pre: u8, pub prefab_path_remote_hash_suffix: u32, + pub controller_path_hash_pre: u8, pub controller_path_hash_suffix: u32, + pub controller_path_remote_hash_pre: u8, pub controller_path_remote_hash_suffix: u32, + + pub prefab_path_ragdoll_hash_pre: u8, + pub prefab_path_ragdoll_hash_suffix: u32, + + pub script_data_path_hash_pre: u8, + pub script_data_path_hash_suffix: u32, + + pub combat_config_hash_pre: u8, + pub combat_config_hash_suffix: u32, + + pub manekin_path_hash_pre: u8, + pub manekin_path_hash_suffix: u32, + + pub coop_pic_name_hash_pre: Option, + pub coop_pic_name_hash_suffix: Option, + + pub manekin_json_config_hash_pre: u8, + pub manekin_json_config_hash_suffix: u32,*/ + //pub camp_id: Option, // Avatars don't have these - pub lod_pattern_name: String, + pub lod_pattern_name: Option, // Creature fields pub hp_base: f32, @@ -71,14 +111,9 @@ pub struct Avatar { //pub prop_grow_curves: Vec, // TODO: unify with monster! - pub prefab_path_ragdoll_hash_pre: u8, - pub prefab_path_ragdoll_hash_suffix: u32, - // Avatar fields pub use_type: Option, // TODO: actually an enum pub body_type: String, // TODO: actually an enum - pub script_data_path_hash_pre: u8, - pub script_data_path_hash_suffix: u32, pub icon_name: String, pub side_icon_name: String, pub quality_type: String, // TODO: actually an enum @@ -87,25 +122,17 @@ pub struct Avatar { pub heal_add: f32, #[serde(default)] pub healed_add: f32, - pub combat_config_hash_pre: u8, - pub combat_config_hash_suffix: u32, #[serde(default)] pub is_range_attack: bool, pub initial_weapon: u32, pub weapon_type: String, // TODO: actually an enum - pub manekin_path_hash_pre: u8, - pub manekin_path_hash_suffix: u32, pub image_name: String, #[serde(flatten)] // Those fields are present or absent all together, so we grouped them pub avatar_gacha_hashes: Option, - pub coop_pic_name_hash_pre: Option, - pub coop_pic_name_hash_suffix: Option, pub cutscene_show: String, pub skill_depot_id: u32, pub stamina_recover_speed: f32, pub cand_skill_depot_ids: Vec, - pub manekin_json_config_hash_pre: u8, - pub manekin_json_config_hash_suffix: u32, pub manekin_motion_config: u32, pub desc_text_map_hash: u32, pub avatar_identity_type: Option, // TODO: actually an enum diff --git a/src/jsonmanager/avatar_skill.rs b/src/jsonmanager/avatar_skill.rs index f7fa478..f64d871 100644 --- a/src/jsonmanager/avatar_skill.rs +++ b/src/jsonmanager/avatar_skill.rs @@ -8,7 +8,7 @@ pub struct AvatarSkill { pub ability_name: String, pub desc_text_map_hash: u32, pub skill_icon: String, - pub cost_stamina: Option, + pub cost_stamina: Option, pub max_charge_num: u32, pub trigger_id: Option, pub lock_shape: String, // TODO: probably an enum diff --git a/src/jsonmanager/monster.rs b/src/jsonmanager/monster.rs index ac09981..3f6e384 100644 --- a/src/jsonmanager/monster.rs +++ b/src/jsonmanager/monster.rs @@ -60,7 +60,7 @@ pub struct Monster { pub exclude_weathers: String, #[serde(rename = "FeatureTagGroupID")] - pub feature_tag_group_id: u32, + pub feature_tag_group_id: Option, #[serde(rename = "MpPropID")] pub mp_prop_id: u32, @@ -89,4 +89,4 @@ pub struct Monster { //pub prop_grow_curves: Vec, pub prop_grow_curves: Vec, -} \ No newline at end of file +}