From 71aadaeb9494f2ce13b5c0b86a144d6a9231e93d Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 3 Oct 2023 21:02:10 -0300 Subject: [PATCH 1/5] add base62 encoding module with int support --- src/api/HPMapi.c | 1 + src/char/HPMchar.c | 1 + src/common/HPM.c | 1 + src/common/HPMDataCheck.h | 5 + src/common/HPMSymbols.inc.h | 7 + src/common/Makefile.in | 4 +- src/common/base62.c | 108 ++++++++++++++ src/common/base62.h | 43 ++++++ src/common/core.c | 2 + src/login/HPMlogin.c | 1 + src/map/HPMmap.c | 1 + src/plugins/HPMHooking.c | 1 + src/plugins/HPMHooking/HPMHooking.Defs.inc | 4 + .../HPMHooking_api.HPMHooksCore.inc | 5 + .../HPMHooking_api.HookingPoints.inc | 2 + .../HPMHooking/HPMHooking_api.Hooks.inc | 28 ++++ .../HPMHooking/HPMHooking_api.sources.inc | 1 + .../HPMHooking_char.HPMHooksCore.inc | 5 + .../HPMHooking_char.HookingPoints.inc | 2 + .../HPMHooking/HPMHooking_char.Hooks.inc | 28 ++++ .../HPMHooking/HPMHooking_char.sources.inc | 1 + .../HPMHooking_login.HPMHooksCore.inc | 5 + .../HPMHooking_login.HookingPoints.inc | 2 + .../HPMHooking/HPMHooking_login.Hooks.inc | 28 ++++ .../HPMHooking/HPMHooking_login.sources.inc | 1 + .../HPMHooking_map.HPMHooksCore.inc | 5 + .../HPMHooking_map.HookingPoints.inc | 2 + .../HPMHooking/HPMHooking_map.Hooks.inc | 28 ++++ .../HPMHooking/HPMHooking_map.sources.inc | 1 + src/test/Makefile.in | 4 +- src/test/test_base62.c | 133 ++++++++++++++++++ 31 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 src/common/base62.c create mode 100644 src/common/base62.h create mode 100644 src/test/test_base62.c diff --git a/src/api/HPMapi.c b/src/api/HPMapi.c index 59f5128f713..091f35c732c 100644 --- a/src/api/HPMapi.c +++ b/src/api/HPMapi.c @@ -25,6 +25,7 @@ #include "common/cbasetypes.h" #include "common/HPMi.h" +#include "common/base62.h" #include "common/chunked/rfifo.h" #include "common/conf.h" #include "common/console.h" diff --git a/src/char/HPMchar.c b/src/char/HPMchar.c index 3991af478b1..64e26d0ae8f 100644 --- a/src/char/HPMchar.c +++ b/src/char/HPMchar.c @@ -48,6 +48,7 @@ #include "char/pincode.h" #include "common/HPMi.h" +#include "common/base62.h" #include "common/conf.h" #include "common/console.h" #include "common/core.h" diff --git a/src/common/HPM.c b/src/common/HPM.c index 4b2c13bcb6a..80fdbf18d9f 100644 --- a/src/common/HPM.c +++ b/src/common/HPM.c @@ -22,6 +22,7 @@ #include "config/core.h" // CONSOLE_INPUT #include "HPM.h" +#include "common/base62.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/console.h" diff --git a/src/common/HPMDataCheck.h b/src/common/HPMDataCheck.h index 4ab945b5ad9..a6c91dd1455 100644 --- a/src/common/HPMDataCheck.h +++ b/src/common/HPMDataCheck.h @@ -249,6 +249,11 @@ HPExport const struct s_HPMDataCheck HPMDataCheck[] = { #else #define COMMON_APIPACKETS_H #endif // COMMON_APIPACKETS_H + #ifdef COMMON_BASE62_H + { "base62_interface", sizeof(struct base62_interface), SERVER_TYPE_ALL }, + #else + #define COMMON_BASE62_H + #endif // COMMON_BASE62_H #ifdef COMMON_CHARMAPPACKETS_H { "PACKET_CHARMAP_AGENCY_JOIN_PARTY", sizeof(struct PACKET_CHARMAP_AGENCY_JOIN_PARTY), SERVER_TYPE_ALL }, { "PACKET_CHARMAP_GUILD_EMBLEM", sizeof(struct PACKET_CHARMAP_GUILD_EMBLEM), SERVER_TYPE_ALL }, diff --git a/src/common/HPMSymbols.inc.h b/src/common/HPMSymbols.inc.h index 39acc3d6527..5769bf7f1e5 100644 --- a/src/common/HPMSymbols.inc.h +++ b/src/common/HPMSymbols.inc.h @@ -47,6 +47,9 @@ struct api_interface *api; #ifdef MAP_ATCOMMAND_H /* atcommand */ struct atcommand_interface *atcommand; #endif // MAP_ATCOMMAND_H +#ifdef COMMON_BASE62_H /* base62 */ +struct base62_interface *base62; +#endif // COMMON_BASE62_H #ifdef MAP_BATTLE_H /* battle */ struct battle_interface *battle; #endif // MAP_BATTLE_H @@ -397,6 +400,10 @@ HPExport const char *HPM_shared_symbols(int server_type) if ((server_type&(SERVER_TYPE_MAP)) != 0 && !HPM_SYMBOL("atcommand", atcommand)) return "atcommand"; #endif // MAP_ATCOMMAND_H +#ifdef COMMON_BASE62_H /* base62 */ + if ((server_type&(SERVER_TYPE_ALL)) != 0 && !HPM_SYMBOL("base62", base62)) + return "base62"; +#endif // COMMON_BASE62_H #ifdef MAP_BATTLE_H /* battle */ if ((server_type&(SERVER_TYPE_MAP)) != 0 && !HPM_SYMBOL("battle", battle)) return "battle"; diff --git a/src/common/Makefile.in b/src/common/Makefile.in index fa466c10477..d3ae225e582 100644 --- a/src/common/Makefile.in +++ b/src/common/Makefile.in @@ -50,7 +50,7 @@ MT19937AR_D = $(THIRDPARTY_D)/mt19937ar MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h -COMMON_SHARED_C = conf.c db.c des.c ers.c extraconf.c grfio.c HPM.c mapindex.c md5calc.c \ +COMMON_SHARED_C = base62.c conf.c db.c des.c ers.c extraconf.c grfio.c HPM.c mapindex.c md5calc.c \ mutex.c nullpo.c packets.c random.c showmsg.c strlib.c \ sysinfo.c thread.c timer.c utils.c COMMON_C = $(COMMON_SHARED_C) @@ -58,7 +58,7 @@ COMMON_SHARED_OBJ = $(patsubst %.c,%.o,$(COMMON_SHARED_C)) COMMON_OBJ = $(addprefix obj_all/, $(COMMON_SHARED_OBJ) \ console.o core.o memmgr.o socket.o) COMMON_C += console.c core.c memmgr.c socket.c -COMMON_H = atomic.h cbasetypes.h conf.h console.h core.h db.h des.h ers.h extraconf.h \ +COMMON_H = atomic.h cbasetypes.h base62.h conf.h console.h core.h db.h des.h ers.h extraconf.h \ grfio.h hercules.h HPM.h HPMi.h memmgr.h memmgr_inc.h mapindex.h \ md5calc.h mmo.h mutex.h nullpo.h packets.h packets_len.h packets_struct.h random.h \ showmsg.h socket.h spinlock.h sql.h strlib.h sysinfo.h thread.h \ diff --git a/src/common/base62.c b/src/common/base62.c new file mode 100644 index 00000000000..9e1bd211035 --- /dev/null +++ b/src/common/base62.c @@ -0,0 +1,108 @@ +/** + * This file is part of Hercules. + * http://herc.ws - http://github.com/HerculesWS/Hercules + * + * Copyright (C) 2023 Hercules Dev Team + * Copyright (C) 2023 KirieZ + * + * Hercules is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#define HERCULES_CORE + +#include "base62.h" + +#include "common/nullpo.h" +#include "common/utils.h" + +static struct base62_interface base62_s; +struct base62_interface *base62; + +/** + * Base 62 conversion table + */ +static char base62tbl[62] = { + '0','1','2','3','4','5','6','7','8', + '9','a','b','c','d','e','f','g','h', + 'i','j','k','l','m','n','o','p','q', + 'r','s','t','u','v','w','x','y','z', + 'A','B','C','D','E','F','G','H','I', + 'J','K','L','M','N','O','P','Q','R', + 'S','T','U','V','W','X','Y','Z' +}; + +/** + * base62-Encode `value` into `buf`, which has a size of `buf_len`. + * If the base62 string is shorter than `min_len`, left pads it with 0 characters. + * + * @remark + * - `buf` will be NULL-terminated, so the maximum number of characters in buffer is always `buf_len` - 1. + * + * @param value value to be encoded + * @param buf buffer where to write the values + * @param min_len minimum length of the string (left padded with 0 when needed) + * @param buf_len buffer size (including the NULL termination) + * @returns true if value was fully encoded, false if something went wrong + */ +static bool base62_encode_int_padded(int value, char *buf, int min_len, int buf_len) +{ + nullpo_retr(false, buf); + Assert_retr(false, min_len < buf_len); + Assert_retr(false, buf_len >= 2); + + // if caller ignores an error, at least it gets a NULL-terminated string + buf[0] = '\0'; + + char temp_buf[BASE62_INT_BUFFER_LEN] = { 0 }; + int max_idx = cap_value(buf_len - 2, 0, BASE62_INT_BUFFER_LEN - 2); + + int idx = 0; + do { + temp_buf[idx] = base62tbl[value % 62]; + value /= 62; + idx++; + } while (idx <= max_idx && value > 0); + + Assert_retr(false, (value == 0)); + + int final_buf_idx = 0; + + if (idx < min_len) { + int pad_size = min_len - idx; + for (int i = 0; i < pad_size; ++i) { + buf[final_buf_idx] = base62tbl[0]; + final_buf_idx++; + } + } + + idx--; + + // reverse the string to get the base62-encoded value. + while (idx >= 0) { + buf[final_buf_idx] = temp_buf[idx]; + final_buf_idx++; + idx--; + } + buf[final_buf_idx] = '\0'; + + return (value == 0); +} + +/** + * + **/ +void base62_defaults(void) +{ + base62 = &base62_s; + base62->encode_int_padded = base62_encode_int_padded; +} diff --git a/src/common/base62.h b/src/common/base62.h new file mode 100644 index 00000000000..c72aa865c86 --- /dev/null +++ b/src/common/base62.h @@ -0,0 +1,43 @@ +/** + * This file is part of Hercules. + * http://herc.ws - http://github.com/HerculesWS/Hercules + * + * Copyright (C) 2023-2023 Hercules Dev Team + * + * Hercules is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef COMMON_BASE62_H +#define COMMON_BASE62_H + +#include "common/hercules.h" + +/** + * Minimum size for a buffer to encode UINT32_MAX, including NULL-terminator + */ +#define BASE62_INT_BUFFER_LEN 7 + +/** + * The base62 interface + **/ +struct base62_interface { + bool (*encode_int_padded) (int value, char *buf, int min_len, int buf_len); +}; + +#ifdef HERCULES_CORE +void base62_defaults(void); +#endif // HERCULES_CORE + +HPShared struct base62_interface *base62; + +#endif // COMMON_BASE62_H diff --git a/src/common/core.c b/src/common/core.c index 6e2da92c047..7191655d2e5 100644 --- a/src/common/core.c +++ b/src/common/core.c @@ -24,6 +24,7 @@ #include "core.h" #include "common/HPM.h" +#include "common/base62.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/console.h" @@ -266,6 +267,7 @@ static void core_defaults(void) rnd_defaults(); md5_defaults(); thread_defaults(); + base62_defaults(); } /** diff --git a/src/login/HPMlogin.c b/src/login/HPMlogin.c index f9b8cf1e395..bc52e28f906 100644 --- a/src/login/HPMlogin.c +++ b/src/login/HPMlogin.c @@ -34,6 +34,7 @@ #include "login/packets_ac_struct.h" #include "login/packets_ca_struct.h" #include "common/HPMi.h" +#include "common/base62.h" #include "common/conf.h" #include "common/console.h" #include "common/core.h" diff --git a/src/map/HPMmap.c b/src/map/HPMmap.c index 29f5bf0372c..63fe503287f 100644 --- a/src/map/HPMmap.c +++ b/src/map/HPMmap.c @@ -25,6 +25,7 @@ #include "common/cbasetypes.h" #include "common/HPMi.h" +#include "common/base62.h" #include "common/conf.h" #include "common/console.h" #include "common/core.h" diff --git a/src/plugins/HPMHooking.c b/src/plugins/HPMHooking.c index 53bfc413aa5..ed50c95011a 100644 --- a/src/plugins/HPMHooking.c +++ b/src/plugins/HPMHooking.c @@ -156,6 +156,7 @@ PRAGMA_GCC5(GCC diagnostic ignored "-Wsuggest-attribute=format") #error HPMHooking plugin needs to be compiled for a specific server type. Please make sure your Makefiles are up to date. #endif PRAGMA_GCC5(GCC diagnostic pop) +#include "common/base62.h" #include "common/conf.h" #include "common/console.h" #include "common/db.h" diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc index 70da0fb6e8e..70096f4b7be 100644 --- a/src/plugins/HPMHooking/HPMHooking.Defs.inc +++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc @@ -443,6 +443,10 @@ typedef const char* (*HPMHOOK_post_atcommand_msgfd) (const char* retVal___, int typedef const char* (*HPMHOOK_pre_atcommand_msgsd) (struct map_session_data **sd, int *msg_number); typedef const char* (*HPMHOOK_post_atcommand_msgsd) (const char* retVal___, struct map_session_data *sd, int msg_number); #endif // MAP_ATCOMMAND_H +#ifdef COMMON_BASE62_H /* base62 */ +typedef bool (*HPMHOOK_pre_base62_encode_int_padded) (int *value, char **buf, int *min_len, int *buf_len); +typedef bool (*HPMHOOK_post_base62_encode_int_padded) (bool retVal___, int value, char *buf, int min_len, int buf_len); +#endif // COMMON_BASE62_H #ifdef MAP_BATTLE_H /* battle */ typedef void (*HPMHOOK_pre_battle_init) (bool *minimal); typedef void (*HPMHOOK_post_battle_init) (bool minimal); diff --git a/src/plugins/HPMHooking/HPMHooking_api.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_api.HPMHooksCore.inc index 8665eab351e..f89625b9122 100644 --- a/src/plugins/HPMHooking/HPMHooking_api.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_api.HPMHooksCore.inc @@ -212,6 +212,8 @@ struct { struct HPMHookPoint *HP_api_check_connect_login_server_post; struct HPMHookPoint *HP_api_do_shutdown_pre; struct HPMHookPoint *HP_api_do_shutdown_post; + struct HPMHookPoint *HP_base62_encode_int_padded_pre; + struct HPMHookPoint *HP_base62_encode_int_padded_post; struct HPMHookPoint *HP_cmdline_init_pre; struct HPMHookPoint *HP_cmdline_init_post; struct HPMHookPoint *HP_cmdline_final_pre; @@ -1221,6 +1223,8 @@ struct { int HP_api_check_connect_login_server_post; int HP_api_do_shutdown_pre; int HP_api_do_shutdown_post; + int HP_base62_encode_int_padded_pre; + int HP_base62_encode_int_padded_post; int HP_cmdline_init_pre; int HP_cmdline_init_post; int HP_cmdline_final_pre; @@ -2048,6 +2052,7 @@ struct { struct aclif_interface aclif; struct aloginif_interface aloginif; struct api_interface api; + struct base62_interface base62; struct cmdline_interface cmdline; struct console_interface console; struct core_interface core; diff --git a/src/plugins/HPMHooking/HPMHooking_api.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_api.HookingPoints.inc index f7fa51b97fe..f2fd76a8b3f 100644 --- a/src/plugins/HPMHooking/HPMHooking_api.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_api.HookingPoints.inc @@ -123,6 +123,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(api->config_read_inter, HP_api_config_read_inter) }, { HP_POP(api->check_connect_login_server, HP_api_check_connect_login_server) }, { HP_POP(api->do_shutdown, HP_api_do_shutdown) }, +/* base62_interface */ + { HP_POP(base62->encode_int_padded, HP_base62_encode_int_padded) }, /* cmdline_interface */ { HP_POP(cmdline->init, HP_cmdline_init) }, { HP_POP(cmdline->final, HP_cmdline_final) }, diff --git a/src/plugins/HPMHooking/HPMHooking_api.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_api.Hooks.inc index 89b2d9c78fa..fb35c8ad7f6 100644 --- a/src/plugins/HPMHooking/HPMHooking_api.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_api.Hooks.inc @@ -2521,6 +2521,34 @@ void HP_api_do_shutdown(void) { } return; } +/* base62_interface */ +bool HP_base62_encode_int_padded(int value, char *buf, int min_len, int buf_len) { + int hIndex = 0; + bool retVal___ = false; + if (HPMHooks.count.HP_base62_encode_int_padded_pre > 0) { + bool (*preHookFunc) (int *value, char **buf, int *min_len, int *buf_len); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_base62_encode_int_padded_pre[hIndex].func; + retVal___ = preHookFunc(&value, &buf, &min_len, &buf_len); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.base62.encode_int_padded(value, buf, min_len, buf_len); + } + if (HPMHooks.count.HP_base62_encode_int_padded_post > 0) { + bool (*postHookFunc) (bool retVal___, int value, char *buf, int min_len, int buf_len); + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_base62_encode_int_padded_post[hIndex].func; + retVal___ = postHookFunc(retVal___, value, buf, min_len, buf_len); + } + } + return retVal___; +} /* cmdline_interface */ void HP_cmdline_init(void) { int hIndex = 0; diff --git a/src/plugins/HPMHooking/HPMHooking_api.sources.inc b/src/plugins/HPMHooking/HPMHooking_api.sources.inc index 201153a6250..7ae0c859003 100644 --- a/src/plugins/HPMHooking/HPMHooking_api.sources.inc +++ b/src/plugins/HPMHooking/HPMHooking_api.sources.inc @@ -29,6 +29,7 @@ HPMHooks.source.HCache = *HCache; HPMHooks.source.aclif = *aclif; HPMHooks.source.aloginif = *aloginif; HPMHooks.source.api = *api; +HPMHooks.source.base62 = *base62; HPMHooks.source.cmdline = *cmdline; HPMHooks.source.console = *console; HPMHooks.source.core = *core; diff --git a/src/plugins/HPMHooking/HPMHooking_char.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_char.HPMHooksCore.inc index 8ced4ed0315..291c2ba994c 100644 --- a/src/plugins/HPMHooking/HPMHooking_char.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_char.HPMHooksCore.inc @@ -32,6 +32,8 @@ struct { struct HPMHookPoint *HP_HCache_check_post; struct HPMHookPoint *HP_HCache_open_pre; struct HPMHookPoint *HP_HCache_open_post; + struct HPMHookPoint *HP_base62_encode_int_padded_pre; + struct HPMHookPoint *HP_base62_encode_int_padded_post; struct HPMHookPoint *HP_capiif_init_pre; struct HPMHookPoint *HP_capiif_init_post; struct HPMHookPoint *HP_capiif_final_pre; @@ -1801,6 +1803,8 @@ struct { int HP_HCache_check_post; int HP_HCache_open_pre; int HP_HCache_open_post; + int HP_base62_encode_int_padded_pre; + int HP_base62_encode_int_padded_post; int HP_capiif_init_pre; int HP_capiif_init_post; int HP_capiif_final_pre; @@ -3565,6 +3569,7 @@ struct { struct { struct HCache_interface HCache; + struct base62_interface base62; struct capiif_interface capiif; struct char_interface chr; struct cmdline_interface cmdline; diff --git a/src/plugins/HPMHooking/HPMHooking_char.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_char.HookingPoints.inc index 2970fe4b6a8..af5c39062e0 100644 --- a/src/plugins/HPMHooking/HPMHooking_char.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_char.HookingPoints.inc @@ -30,6 +30,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(HCache->init, HP_HCache_init) }, { HP_POP(HCache->check, HP_HCache_check) }, { HP_POP(HCache->open, HP_HCache_open) }, +/* base62_interface */ + { HP_POP(base62->encode_int_padded, HP_base62_encode_int_padded) }, /* capiif_interface */ { HP_POP(capiif->init, HP_capiif_init) }, { HP_POP(capiif->final, HP_capiif_final) }, diff --git a/src/plugins/HPMHooking/HPMHooking_char.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_char.Hooks.inc index f615e59337b..ca6dbd63b73 100644 --- a/src/plugins/HPMHooking/HPMHooking_char.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_char.Hooks.inc @@ -106,6 +106,34 @@ FILE* HP_HCache_open(const char *file, const char *opt) { } return retVal___; } +/* base62_interface */ +bool HP_base62_encode_int_padded(int value, char *buf, int min_len, int buf_len) { + int hIndex = 0; + bool retVal___ = false; + if (HPMHooks.count.HP_base62_encode_int_padded_pre > 0) { + bool (*preHookFunc) (int *value, char **buf, int *min_len, int *buf_len); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_base62_encode_int_padded_pre[hIndex].func; + retVal___ = preHookFunc(&value, &buf, &min_len, &buf_len); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.base62.encode_int_padded(value, buf, min_len, buf_len); + } + if (HPMHooks.count.HP_base62_encode_int_padded_post > 0) { + bool (*postHookFunc) (bool retVal___, int value, char *buf, int min_len, int buf_len); + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_base62_encode_int_padded_post[hIndex].func; + retVal___ = postHookFunc(retVal___, value, buf, min_len, buf_len); + } + } + return retVal___; +} /* capiif_interface */ void HP_capiif_init(void) { int hIndex = 0; diff --git a/src/plugins/HPMHooking/HPMHooking_char.sources.inc b/src/plugins/HPMHooking/HPMHooking_char.sources.inc index 587c32509d1..34220ae13da 100644 --- a/src/plugins/HPMHooking/HPMHooking_char.sources.inc +++ b/src/plugins/HPMHooking/HPMHooking_char.sources.inc @@ -26,6 +26,7 @@ /* GENERATED FILE DO NOT EDIT */ HPMHooks.source.HCache = *HCache; +HPMHooks.source.base62 = *base62; HPMHooks.source.capiif = *capiif; HPMHooks.source.chr = *chr; HPMHooks.source.cmdline = *cmdline; diff --git a/src/plugins/HPMHooking/HPMHooking_login.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_login.HPMHooksCore.inc index 8cdc1cf3dbb..123dc545cb2 100644 --- a/src/plugins/HPMHooking/HPMHooking_login.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_login.HPMHooksCore.inc @@ -70,6 +70,8 @@ struct { struct HPMHookPoint *HP_account_db_sql_iter_next_post; struct HPMHookPoint *HP_account_db_read_inter_pre; struct HPMHookPoint *HP_account_db_read_inter_post; + struct HPMHookPoint *HP_base62_encode_int_padded_pre; + struct HPMHookPoint *HP_base62_encode_int_padded_post; struct HPMHookPoint *HP_cmdline_init_pre; struct HPMHookPoint *HP_cmdline_init_post; struct HPMHookPoint *HP_cmdline_final_pre; @@ -981,6 +983,8 @@ struct { int HP_account_db_sql_iter_next_post; int HP_account_db_read_inter_pre; int HP_account_db_read_inter_post; + int HP_base62_encode_int_padded_pre; + int HP_base62_encode_int_padded_post; int HP_cmdline_init_pre; int HP_cmdline_init_post; int HP_cmdline_final_pre; @@ -1850,6 +1854,7 @@ struct { struct { struct HCache_interface HCache; struct account_interface account; + struct base62_interface base62; struct cmdline_interface cmdline; struct console_interface console; struct core_interface core; diff --git a/src/plugins/HPMHooking/HPMHooking_login.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_login.HookingPoints.inc index 5c67f72a4ab..ce10dbf1799 100644 --- a/src/plugins/HPMHooking/HPMHooking_login.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_login.HookingPoints.inc @@ -50,6 +50,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(account->db_sql_iter_destroy, HP_account_db_sql_iter_destroy) }, { HP_POP(account->db_sql_iter_next, HP_account_db_sql_iter_next) }, { HP_POP(account->db_read_inter, HP_account_db_read_inter) }, +/* base62_interface */ + { HP_POP(base62->encode_int_padded, HP_base62_encode_int_padded) }, /* cmdline_interface */ { HP_POP(cmdline->init, HP_cmdline_init) }, { HP_POP(cmdline->final, HP_cmdline_final) }, diff --git a/src/plugins/HPMHooking/HPMHooking_login.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_login.Hooks.inc index 55878402b06..a9264a63834 100644 --- a/src/plugins/HPMHooking/HPMHooking_login.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_login.Hooks.inc @@ -616,6 +616,34 @@ bool HP_account_db_read_inter(AccountDB_SQL *db, const char *filename, bool impo } return retVal___; } +/* base62_interface */ +bool HP_base62_encode_int_padded(int value, char *buf, int min_len, int buf_len) { + int hIndex = 0; + bool retVal___ = false; + if (HPMHooks.count.HP_base62_encode_int_padded_pre > 0) { + bool (*preHookFunc) (int *value, char **buf, int *min_len, int *buf_len); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_base62_encode_int_padded_pre[hIndex].func; + retVal___ = preHookFunc(&value, &buf, &min_len, &buf_len); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.base62.encode_int_padded(value, buf, min_len, buf_len); + } + if (HPMHooks.count.HP_base62_encode_int_padded_post > 0) { + bool (*postHookFunc) (bool retVal___, int value, char *buf, int min_len, int buf_len); + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_base62_encode_int_padded_post[hIndex].func; + retVal___ = postHookFunc(retVal___, value, buf, min_len, buf_len); + } + } + return retVal___; +} /* cmdline_interface */ void HP_cmdline_init(void) { int hIndex = 0; diff --git a/src/plugins/HPMHooking/HPMHooking_login.sources.inc b/src/plugins/HPMHooking/HPMHooking_login.sources.inc index 027e2f41105..71da4e8ea25 100644 --- a/src/plugins/HPMHooking/HPMHooking_login.sources.inc +++ b/src/plugins/HPMHooking/HPMHooking_login.sources.inc @@ -27,6 +27,7 @@ HPMHooks.source.HCache = *HCache; HPMHooks.source.account = *account; +HPMHooks.source.base62 = *base62; HPMHooks.source.cmdline = *cmdline; HPMHooks.source.console = *console; HPMHooks.source.core = *core; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc index e0081109e37..9875ec0a5b0 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc @@ -212,6 +212,8 @@ struct { struct HPMHookPoint *HP_atcommand_msgfd_post; struct HPMHookPoint *HP_atcommand_msgsd_pre; struct HPMHookPoint *HP_atcommand_msgsd_post; + struct HPMHookPoint *HP_base62_encode_int_padded_pre; + struct HPMHookPoint *HP_base62_encode_int_padded_post; struct HPMHookPoint *HP_battle_init_pre; struct HPMHookPoint *HP_battle_init_post; struct HPMHookPoint *HP_battle_final_pre; @@ -7743,6 +7745,8 @@ struct { int HP_atcommand_msgfd_post; int HP_atcommand_msgsd_pre; int HP_atcommand_msgsd_post; + int HP_base62_encode_int_padded_pre; + int HP_base62_encode_int_padded_post; int HP_battle_init_pre; int HP_battle_init_post; int HP_battle_final_pre; @@ -15091,6 +15095,7 @@ struct { struct HCache_interface HCache; struct achievement_interface achievement; struct atcommand_interface atcommand; + struct base62_interface base62; struct battle_interface battle; struct battleground_interface bg; struct buyingstore_interface buyingstore; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc index f2760cd22be..fca1941bf13 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc @@ -122,6 +122,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(atcommand->expand_message_table, HP_atcommand_expand_message_table) }, { HP_POP(atcommand->msgfd, HP_atcommand_msgfd) }, { HP_POP(atcommand->msgsd, HP_atcommand_msgsd) }, +/* base62_interface */ + { HP_POP(base62->encode_int_padded, HP_base62_encode_int_padded) }, /* battle_interface */ { HP_POP(battle->init, HP_battle_init) }, { HP_POP(battle->final, HP_battle_final) }, diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index b9a1937eec6..b173e9b6b48 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -2537,6 +2537,34 @@ const char* HP_atcommand_msgsd(struct map_session_data *sd, int msg_number) { } return retVal___; } +/* base62_interface */ +bool HP_base62_encode_int_padded(int value, char *buf, int min_len, int buf_len) { + int hIndex = 0; + bool retVal___ = false; + if (HPMHooks.count.HP_base62_encode_int_padded_pre > 0) { + bool (*preHookFunc) (int *value, char **buf, int *min_len, int *buf_len); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_base62_encode_int_padded_pre[hIndex].func; + retVal___ = preHookFunc(&value, &buf, &min_len, &buf_len); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.base62.encode_int_padded(value, buf, min_len, buf_len); + } + if (HPMHooks.count.HP_base62_encode_int_padded_post > 0) { + bool (*postHookFunc) (bool retVal___, int value, char *buf, int min_len, int buf_len); + for (hIndex = 0; hIndex < HPMHooks.count.HP_base62_encode_int_padded_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_base62_encode_int_padded_post[hIndex].func; + retVal___ = postHookFunc(retVal___, value, buf, min_len, buf_len); + } + } + return retVal___; +} /* battle_interface */ void HP_battle_init(bool minimal) { int hIndex = 0; diff --git a/src/plugins/HPMHooking/HPMHooking_map.sources.inc b/src/plugins/HPMHooking/HPMHooking_map.sources.inc index 5b46deb6f33..9aa0b14402d 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.sources.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.sources.inc @@ -28,6 +28,7 @@ HPMHooks.source.HCache = *HCache; HPMHooks.source.achievement = *achievement; HPMHooks.source.atcommand = *atcommand; +HPMHooks.source.base62 = *base62; HPMHooks.source.battle = *battle; HPMHooks.source.bg = *bg; HPMHooks.source.buyingstore = *buyingstore; diff --git a/src/test/Makefile.in b/src/test/Makefile.in index 2f43d3554c3..ec44b6439d5 100644 --- a/src/test/Makefile.in +++ b/src/test/Makefile.in @@ -53,12 +53,12 @@ MT19937AR_D = $(THIRDPARTY_D)/mt19937ar MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h -TEST_C = test_libconfig.c test_spinlock.c test_chunked.c +TEST_C = test_libconfig.c test_spinlock.c test_chunked.c test_base62.c TEST_OBJ = $(addprefix obj/, $(patsubst %c,%o,%(TEST_C))) TEST_H = TEST_DEPENDS = $(COMMON_D)/obj_sql/common_sql.a $(COMMON_D)/obj_all/common.a $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) $(LIBBACKTRACE_OBJ) $(SYSINFO_INC) -TESTS_ALL = test_libconfig test_spinlock test_chunked +TESTS_ALL = test_libconfig test_spinlock test_chunked test_base62 @SET_MAKE@ diff --git a/src/test/test_base62.c b/src/test/test_base62.c new file mode 100644 index 00000000000..13c65f7f371 --- /dev/null +++ b/src/test/test_base62.c @@ -0,0 +1,133 @@ +/** + * This file is part of Hercules. + * http://herc.ws - http://github.com/HerculesWS/Hercules + * + * Copyright (C) 2023 Hercules Dev Team + * Copyright (C) 2023 KirieZ + * + * Hercules is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define HERCULES_CORE + +#include "common/cbasetypes.h" +#include "common/base62.h" +#include "common/core.h" +#include "common/showmsg.h" +#include "common/strlib.h" + +#include + +#define TEST(name, function) do { \ + ShowMessage("-------------------------------------------------------------------------------\n"); \ + ShowNotice("Testing %s...\n", (name)); \ + if (!(function)()) { \ + ShowError("Failed.\n"); \ + ShowMessage("===============================================================================\n"); \ + ShowFatalError("Failure. Aborting further tests.\n"); \ + exit(EXIT_FAILURE); \ + } \ + ShowInfo("Test passed.\n"); \ +} while (false) + +#define context(message, ...) do { \ + ShowNotice("\n"); \ + ShowNotice("> " message "\n", ##__VA_ARGS__); \ +} while (false) + +#define expect(formatter, pass_expr, message, actual, expected, ...) do { \ + ShowNotice("\t" message "... ", ##__VA_ARGS__); \ + if (!(pass_expr)) { \ + passed = false; \ + ShowMessage("" CL_RED "Failed" CL_RESET "\n"); \ + ShowNotice("\t\tExpected: " CL_GREEN formatter CL_RESET ",\n", expected); \ + ShowNotice("\t\tReceived: " CL_RED formatter CL_RESET "\n", actual); \ + } else { \ + ShowMessage("" CL_GREEN "Passed" CL_RESET "\n"); \ + } \ +} while (false) + +#define expect_int(message, actual, expected, ...) \ + expect("%d", (actual == expected), message, actual, expected, ##__VA_ARGS__) +#define expect_str(message, actual, expected, ...) \ + expect("%s", (strcmp(actual, expected) == 0), message, actual, expected, ##__VA_ARGS__) + +static bool test_base62_encode_int_padded(void) +{ + bool passed = true; + + { + context("Encoding int '2' in a buffer with min length = 5"); + char output[30]; + bool res = base62->encode_int_padded(2, output, 5, sizeof(output)); + expect_int("To encode successfully", res, true); + expect_str("To pad extra fields with 0", output, "00002"); + expect_int("To have a NULL-terminated buffer", output[6], '\0'); + } + + { + context("Encoding int '1201' in a bufferwith min length = 5 (5 spaces + NULL terminator)"); + char output[30]; + bool res = base62->encode_int_padded(1201, output, 5, sizeof(output)); + expect_int("To encode successfully", res, true); + expect_str("To pad extra fields with 0", output, "000jn"); + expect_int("To have a NULL-terminated buffer", output[6], '\0'); + } + + { + context("Encoding int 'INT_MAX - 1' in a buffer with min length = 5, but enough buffer size"); + char output[30]; + bool res = base62->encode_int_padded(INT_MAX - 1 , output, 5, sizeof(output)); + expect_int("To encode successfully", res, true); + expect_str("to return the encoded value without truncating it", output, "2lkCB0"); + expect_int("To have a NULL-terminated buffer", output[6], '\0'); + } + + { + context("Encoding int 'INT_MAX - 1' in a buffer of length 6 (5 spaces + NULL terminator), which does not support the number"); + char output[6]; + // This will show an assert error to alert server owners that the used buffer is too small + bool res = base62->encode_int_padded(INT_MAX - 1, output, 5, sizeof(output)); + expect_int("To fail the encoding", res, false); + expect_int("To have a NULL-terminated buffer", output[6], '\0'); + } + + return passed; +} + +int do_init(int argc, char **argv) +{ + ShowMessage("===============================================================================\n"); + ShowStatus("Starting tests.\n"); + + TEST("Base62: encode int padded", test_base62_encode_int_padded); + + core->runflag = CORE_ST_STOP; + return EXIT_SUCCESS; +} + +int do_final(void) { + ShowMessage("===============================================================================\n"); + ShowStatus("All tests passed.\n"); + return EXIT_SUCCESS; +} + +void do_abort(void) { } + +void set_server_type(void) +{ + SERVER_TYPE = SERVER_TYPE_UNKNOWN; +} + +void cmdline_args_init_local(void) { } From 22aef0dffbe96a7803778164d0c732f5376e99cc Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 3 Oct 2023 21:09:47 -0300 Subject: [PATCH 2/5] add itemlink formatting function --- src/map/clif.c | 106 ++++++++++++++++++ src/map/clif.h | 3 + src/plugins/HPMHooking/HPMHooking.Defs.inc | 2 + .../HPMHooking_map.HPMHooksCore.inc | 4 + .../HPMHooking_map.HookingPoints.inc | 1 + .../HPMHooking/HPMHooking_map.Hooks.inc | 26 +++++ 6 files changed, 142 insertions(+) diff --git a/src/map/clif.c b/src/map/clif.c index 3f0b91f2b44..e6ba44a8f2e 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -64,6 +64,7 @@ #include "map/vending.h" #include "map/achievement.h" #include "common/HPM.h" +#include "common/base62.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/ers.h" @@ -25955,6 +25956,109 @@ static void clif_parse_adventuterAgencyJoinResult(int fd, struct map_session_dat #endif } +/** + * Creates a "item link" tag () string representing item "it" into buf. + * The resulting format and feature support is client-specific (newer clients supports more). + * + * @param buf buffer where the string will be written to + * @param it item data to be formatted + */ +static void clif_format_itemlink(StringBuf *buf, const struct item *it) +{ + nullpo_retv(buf); + nullpo_retv(it); + + struct item_data *itd = itemdb->search(it->nameid); + if (itd == &itemdb->dummy) { + StrBuf->Printf(buf, "%s", itd->jname); + return; + } + +#if PACKETVER_MAIN_NUM < 20150923 && PACKETVER_RE_NUM < 20150819 && !defined(PACKETVER_ZERO) + // Client-versions that doesn't support , return the item name and ends. + StrBuf->Printf(buf, "%s", itd->jname); + return; +#endif // PACKETVER_MAIN_NUM < 20150923 && PACKETVER_RE_NUM < 20150819 && !defined(PACKETVER_ZERO) + + // Separators that didn't change along the time + static const char ref_sep = '%'; +#if PACKETVER >= 20161116 + static const char view_sep = '&'; +#endif +#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200723 + static const char grade_sep = '\''; +#endif + + // Separators that were updated along the time +#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200723 + static const char *tag_name = "ITEML"; + static const char card_sep = ')'; + static const char optid_sep = '+'; + static const char optpar_sep = ','; + static const char optval_sep = '-'; +#elif PACKETVER_MAIN_NUM >= 20161116 || PACKETVER_RE_NUM >= 20161116 || defined(PACKETVER_ZERO) + static const char *tag_name = "ITEML"; + static const char card_sep = '('; + static const char optid_sep = '*'; + static const char optpar_sep = '+'; + static const char optval_sep = ','; +#else // PACKETVER_MAIN_NUM >= 20150923 || PACKETVER_RE_NUM >= 20150819 || defined(PACKETVER_ZERO) + static const char *tag_name = "ITEM"; + static const char card_sep = '\''; + static const char optid_sep = ')'; + static const char optpar_sep = '*'; + static const char optval_sep = '+'; +#endif + + const bool isequip = itemdb->isequip2(itd); + + char output[BASE62_INT_BUFFER_LEN]; + + /** + * To make it easier to read the string building below. This takes care of getting a base62 str and + * returning an empty string in case something goes wrong. + * @param value value to encode + * @param min_len the minimum length the string should have. Shorter encoded-strings will be padded. + */ + #define get_padded_value(value, min_len) \ + (base62->encode_int_padded(value, output, min_len, sizeof(output)) ? output : "") + + StrBuf->Printf(buf, "<%s>", tag_name); + + StrBuf->Printf(buf, "%s", get_padded_value(itd->equip, 5)); + StrBuf->Printf(buf, "%s", get_padded_value((isequip ? 1 : 0), 1)); + StrBuf->Printf(buf, "%s", get_padded_value(it->nameid, 0)); + if (it->refine > 0) + StrBuf->Printf(buf, "%c%s", ref_sep, get_padded_value(it->refine, 2)); + +#if PACKETVER >= 20161116 + StrBuf->Printf(buf, "%c%s", view_sep, get_padded_value(itd->view_sprite, 2)); +#endif // PACKETVER >= 20161116 + +#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200723 + StrBuf->Printf(buf, "%c%s", grade_sep, get_padded_value(it->grade, 2)); +#endif // PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200723 + + // From tests, when client is writting, it writes only the real slots (e.g. a Knife[3] would write 3 cards) + // but if there are 0 slots or there is a stats gem in "non-slot" positions, it writes all 4 slots. + // Also from tests, always sending the 4 slots works fine. + for (int i = 0; i < MAX_SLOTS; i++) + StrBuf->Printf(buf, "%c%s", card_sep, get_padded_value(it->card[i], 2)); + + for (int i = 0; i < MAX_ITEM_OPTIONS; i++) { + if (it->option[i].index == 0) + continue; + + StrBuf->Printf(buf, "%c%s", optid_sep, get_padded_value(it->option[i].index, 2)); + StrBuf->Printf(buf, "%c%s", optpar_sep, get_padded_value(it->option[i].param, 2)); + StrBuf->Printf(buf, "%c%s", optval_sep, get_padded_value(it->option[i].value, 2)); + } + + StrBuf->Printf(buf, "", tag_name); + + #undef get_padded_value +} + /*========================================== * Main client packet processing function *------------------------------------------*/ @@ -27314,4 +27418,6 @@ void clif_defaults(void) clif->adventurerAgencyResult = clif_adventurerAgencyResult; clif->adventurerAgencyJoinReq = clif_adventurerAgencyJoinReq; clif->pAdventuterAgencyJoinResult = clif_parse_adventuterAgencyJoinResult; + + clif->format_itemlink = clif_format_itemlink; } diff --git a/src/map/clif.h b/src/map/clif.h index 2f340d4387e..f1c9e826603 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -31,6 +31,7 @@ /** * Declarations **/ +struct StringBuf; struct battleground_data; struct channel_data; struct chat_data; @@ -960,6 +961,8 @@ struct clif_interface { const struct s_packet_db *(*packet) (int packet_id); unsigned short (*parse_cmd) ( int fd, struct map_session_data *sd ); unsigned short (*decrypt_cmd) ( int cmd, struct map_session_data *sd ); + /* client-specific logic */ + void (*format_itemlink) (struct StringBuf *buf, const struct item *it); /* auth */ void (*authok) (struct map_session_data *sd); void (*auth_error) (int fd, int errorCode); diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc index 70096f4b7be..506eb410e0e 100644 --- a/src/plugins/HPMHooking/HPMHooking.Defs.inc +++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc @@ -1332,6 +1332,8 @@ typedef unsigned short (*HPMHOOK_pre_clif_parse_cmd) (int *fd, struct map_sessio typedef unsigned short (*HPMHOOK_post_clif_parse_cmd) (unsigned short retVal___, int fd, struct map_session_data *sd); typedef unsigned short (*HPMHOOK_pre_clif_decrypt_cmd) (int *cmd, struct map_session_data **sd); typedef unsigned short (*HPMHOOK_post_clif_decrypt_cmd) (unsigned short retVal___, int cmd, struct map_session_data *sd); +typedef void (*HPMHOOK_pre_clif_format_itemlink) (struct StringBuf **buf, const struct item **it); +typedef void (*HPMHOOK_post_clif_format_itemlink) (struct StringBuf *buf, const struct item *it); typedef void (*HPMHOOK_pre_clif_authok) (struct map_session_data **sd); typedef void (*HPMHOOK_post_clif_authok) (struct map_session_data *sd); typedef void (*HPMHOOK_pre_clif_auth_error) (int *fd, int *errorCode); diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc index 9875ec0a5b0..b4999b57ed1 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc @@ -690,6 +690,8 @@ struct { struct HPMHookPoint *HP_clif_parse_cmd_post; struct HPMHookPoint *HP_clif_decrypt_cmd_pre; struct HPMHookPoint *HP_clif_decrypt_cmd_post; + struct HPMHookPoint *HP_clif_format_itemlink_pre; + struct HPMHookPoint *HP_clif_format_itemlink_post; struct HPMHookPoint *HP_clif_authok_pre; struct HPMHookPoint *HP_clif_authok_post; struct HPMHookPoint *HP_clif_auth_error_pre; @@ -8223,6 +8225,8 @@ struct { int HP_clif_parse_cmd_post; int HP_clif_decrypt_cmd_pre; int HP_clif_decrypt_cmd_post; + int HP_clif_format_itemlink_pre; + int HP_clif_format_itemlink_post; int HP_clif_authok_pre; int HP_clif_authok_post; int HP_clif_auth_error_pre; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc index fca1941bf13..911838face0 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc @@ -370,6 +370,7 @@ struct HookingPointData HookingPoints[] = { { HP_POP(clif->packet, HP_clif_packet) }, { HP_POP(clif->parse_cmd, HP_clif_parse_cmd) }, { HP_POP(clif->decrypt_cmd, HP_clif_decrypt_cmd) }, + { HP_POP(clif->format_itemlink, HP_clif_format_itemlink) }, { HP_POP(clif->authok, HP_clif_authok) }, { HP_POP(clif->auth_error, HP_clif_auth_error) }, { HP_POP(clif->authrefuse, HP_clif_authrefuse) }, diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index b173e9b6b48..9ad33f51497 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -8980,6 +8980,32 @@ unsigned short HP_clif_decrypt_cmd(int cmd, struct map_session_data *sd) { } return retVal___; } +void HP_clif_format_itemlink(struct StringBuf *buf, const struct item *it) { + int hIndex = 0; + if (HPMHooks.count.HP_clif_format_itemlink_pre > 0) { + void (*preHookFunc) (struct StringBuf **buf, const struct item **it); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_format_itemlink_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_clif_format_itemlink_pre[hIndex].func; + preHookFunc(&buf, &it); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return; + } + } + { + HPMHooks.source.clif.format_itemlink(buf, it); + } + if (HPMHooks.count.HP_clif_format_itemlink_post > 0) { + void (*postHookFunc) (struct StringBuf *buf, const struct item *it); + for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_format_itemlink_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_clif_format_itemlink_post[hIndex].func; + postHookFunc(buf, it); + } + } + return; +} void HP_clif_authok(struct map_session_data *sd) { int hIndex = 0; if (HPMHooks.count.HP_clif_authok_pre > 0) { From d7b3ecde0c0578abf945216ad3d9bd729a2ebfd0 Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 3 Oct 2023 21:11:14 -0300 Subject: [PATCH 3/5] add script->array_get_num_member function this function makes it simpler to get a value from a number script array, saving users from needing to handle the stack directly --- src/map/script.c | 19 +++++++++++++ src/map/script.h | 1 + src/plugins/HPMHooking/HPMHooking.Defs.inc | 2 ++ .../HPMHooking_map.HPMHooksCore.inc | 4 +++ .../HPMHooking_map.HookingPoints.inc | 1 + .../HPMHooking/HPMHooking_map.Hooks.inc | 27 +++++++++++++++++++ 6 files changed, 54 insertions(+) diff --git a/src/map/script.c b/src/map/script.c index ce64646bccc..eadc7793eca 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -3641,6 +3641,24 @@ static void script_array_update(struct reg_db *src, int64 num, bool empty) } } +/** + * Given the array array_data, fetches the number value at index. + * + * @param st executing script state + * @param array_data script_data containing the array paramter to have an index fetched from + * @param index index to be fetched + * @returns the value in this index. if the index is not set, 0 is returned (like in the script engine) + */ +static int32 script_array_get_num_member(struct script_state *st, struct script_data *array_data, int index) +{ + uint32 id = reference_getid(array_data); + + int32 value = (int32) h64BPTRSIZE(script->get_val2(st, reference_uid(id, index), reference_getref(array_data))); + script_removetop(st, -1, 0); + + return value; +} + static void set_reg_npcscope_str(struct script_state *st, struct reg_db *n, int64 num, const char *name, const char *str) { if (n) @@ -30368,6 +30386,7 @@ void script_defaults(void) script->array_src = script_array_src; script->array_update = script_array_update; script->array_add_member = script_array_add_member; + script->array_get_num_member = script_array_get_num_member; script->array_remove_member = script_array_remove_member; script->array_delete = script_array_delete; script->array_size = script_array_size; diff --git a/src/map/script.h b/src/map/script.h index 99ca233bbbd..b3afbd0e7e3 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -1097,6 +1097,7 @@ struct script_interface { void (*array_delete) (struct reg_db *src, struct script_array *sa); void (*array_remove_member) (struct reg_db *src, struct script_array *sa, unsigned int idx); void (*array_add_member) (struct script_array *sa, unsigned int idx); + int32 (*array_get_num_member) (struct script_state *st, struct script_data *array_data, int index); unsigned int (*array_size) (struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref); unsigned int (*array_highest_key) (struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref); int (*array_free_db) (union DBKey key, struct DBData *data, va_list ap); diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc index 506eb410e0e..503384ca8e2 100644 --- a/src/plugins/HPMHooking/HPMHooking.Defs.inc +++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc @@ -8166,6 +8166,8 @@ typedef void (*HPMHOOK_pre_script_array_remove_member) (struct reg_db **src, str typedef void (*HPMHOOK_post_script_array_remove_member) (struct reg_db *src, struct script_array *sa, unsigned int idx); typedef void (*HPMHOOK_pre_script_array_add_member) (struct script_array **sa, unsigned int *idx); typedef void (*HPMHOOK_post_script_array_add_member) (struct script_array *sa, unsigned int idx); +typedef int32 (*HPMHOOK_pre_script_array_get_num_member) (struct script_state **st, struct script_data **array_data, int *index); +typedef int32 (*HPMHOOK_post_script_array_get_num_member) (int32 retVal___, struct script_state *st, struct script_data *array_data, int index); typedef unsigned int (*HPMHOOK_pre_script_array_size) (struct script_state **st, struct map_session_data **sd, const char **name, struct reg_db **ref); typedef unsigned int (*HPMHOOK_post_script_array_size) (unsigned int retVal___, struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref); typedef unsigned int (*HPMHOOK_pre_script_array_highest_key) (struct script_state **st, struct map_session_data **sd, const char **name, struct reg_db **ref); diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc index b4999b57ed1..8175f9ce1d1 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc @@ -6094,6 +6094,8 @@ struct { struct HPMHookPoint *HP_script_array_remove_member_post; struct HPMHookPoint *HP_script_array_add_member_pre; struct HPMHookPoint *HP_script_array_add_member_post; + struct HPMHookPoint *HP_script_array_get_num_member_pre; + struct HPMHookPoint *HP_script_array_get_num_member_post; struct HPMHookPoint *HP_script_array_size_pre; struct HPMHookPoint *HP_script_array_size_post; struct HPMHookPoint *HP_script_array_highest_key_pre; @@ -13629,6 +13631,8 @@ struct { int HP_script_array_remove_member_post; int HP_script_array_add_member_pre; int HP_script_array_add_member_post; + int HP_script_array_get_num_member_pre; + int HP_script_array_get_num_member_post; int HP_script_array_size_pre; int HP_script_array_size_post; int HP_script_array_highest_key_pre; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc index 911838face0..0613a2e00c9 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc @@ -3120,6 +3120,7 @@ struct HookingPointData HookingPoints[] = { { HP_POP(script->array_delete, HP_script_array_delete) }, { HP_POP(script->array_remove_member, HP_script_array_remove_member) }, { HP_POP(script->array_add_member, HP_script_array_add_member) }, + { HP_POP(script->array_get_num_member, HP_script_array_get_num_member) }, { HP_POP(script->array_size, HP_script_array_size) }, { HP_POP(script->array_highest_key, HP_script_array_highest_key) }, { HP_POP(script->array_free_db, HP_script_array_free_db) }, diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index 9ad33f51497..a132bf8dfb6 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -81263,6 +81263,33 @@ void HP_script_array_add_member(struct script_array *sa, unsigned int idx) { } return; } +int32 HP_script_array_get_num_member(struct script_state *st, struct script_data *array_data, int index) { + int hIndex = 0; + int32 retVal___ = 0; + if (HPMHooks.count.HP_script_array_get_num_member_pre > 0) { + int32 (*preHookFunc) (struct script_state **st, struct script_data **array_data, int *index); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_script_array_get_num_member_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_script_array_get_num_member_pre[hIndex].func; + retVal___ = preHookFunc(&st, &array_data, &index); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.script.array_get_num_member(st, array_data, index); + } + if (HPMHooks.count.HP_script_array_get_num_member_post > 0) { + int32 (*postHookFunc) (int32 retVal___, struct script_state *st, struct script_data *array_data, int index); + for (hIndex = 0; hIndex < HPMHooks.count.HP_script_array_get_num_member_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_script_array_get_num_member_post[hIndex].func; + retVal___ = postHookFunc(retVal___, st, array_data, index); + } + } + return retVal___; +} unsigned int HP_script_array_size(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref) { int hIndex = 0; unsigned int retVal___ = 0; From 56c195d2fcc62dcf0103a125d3d2f68da3fcc97d Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 3 Oct 2023 21:12:28 -0300 Subject: [PATCH 4/5] add a remark to script_array_size --- src/map/script.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/map/script.c b/src/map/script.c index eadc7793eca..1bc9ad969b7 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -3450,6 +3450,9 @@ static void script_array_ensure_zero(struct script_state *st, struct map_session } /** * Returns array size by ID + * @remarks + * "size" here refers to allocated indexes, where non-zero values are set. + * For the highest used index, see script_array_highest_key **/ static unsigned int script_array_size(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref) { From 06ed027da36133066f60fca0c83888f9cab87982 Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 3 Oct 2023 22:33:10 -0300 Subject: [PATCH 5/5] add getitemlink script command this command creates a client-specific formatted tag with item info --- doc/script_commands.txt | 44 ++++++++++++++++ npc/dev/test.txt | 93 ++++++++++++++++++++++++++++++++++ src/map/script.c | 109 ++++++++++++++++++++++++++++++++++++++-- src/map/script.h | 1 + 4 files changed, 244 insertions(+), 3 deletions(-) diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 8c5d811c600..ac1c5071129 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -3547,6 +3547,50 @@ Example: --------------------------------------- +*getitemlink({, {, {, {, }}}}) +*getitemlink(""{, {, {, {, }}}}) + +Get specially formatted client string that represents an item with the given properties. + +Parameters: +- item_id: Item DB id. May be the constant (e.g.: 4001 or Poring_Card) +- item_name: Item DB name. +- refine: Item refine amount (optional, defaults to 0) +- cards_array: An array containing the cards item IDs. (optional, defaults to 0 / no cards) +- options_array: An array containing option information. (optional, defaults to 0 / no options) + Structure: , , , , , , ... , , +- grade: Item grade (optional, defaults to 0) + + +Example: + + setarray(.@cards, Fabre_Card, 0, 0, Agility1); + setarray(.@options, + VAR_MAXHPAMOUNT, 10, 0, // option 1 + VAR_MAXSPAMOUNT, 20, 0 // option 2 + ); + + // In a recent client, shows something like: + // Here is my very dangerous item: <+10 Vital Knife [Agi +1]> + // + // The text between <> is made by the client and is clickable, showing the item options and grade too. + dispbottom(sprintf("Here is my very dangerous item: %s", getitemlink(Knife, 10, .@cards, .@options, 3))); + end; + + +Client support: +- PACKETVER_MAIN_NUM < 20150923 and PACKETVER_RE_NUM < 20150819 + Only item name is returned. As pure string. +- PACKETVER_MAIN_NUM < 20200916, PACKETVER_RE_NUM < 20200723, PACKETVER_ZERO + Grade is ignored. Returns a clickable text. +- PACKETVER_MAIN_NUM >= 20200916, PACKETVER_RE_NUM >= 20200723 + Grade is also supported. Returns a clickable text. + +Note: The clickable text also depends on where it is used. For example, a "mes" will properly format the item name, + but it won't be clickable. While a "dispbottom" shows it formatted and clickable. This is client-defined. + +--------------------------------------- + *getequipisenableopt() This function checks if the equipped item allows the use of bonus options. diff --git a/npc/dev/test.txt b/npc/dev/test.txt index d459a83b83d..32d0cf1f4a1 100644 --- a/npc/dev/test.txt +++ b/npc/dev/test.txt @@ -173,6 +173,94 @@ function script F_TestCalendarNextTime { return .@local_check && .@utc_check; } +function script F_TestGetItemLink_Etc { + .@str$ = getitemlink(Jellopy, 0, 0, 0, 0); + .@str2$ = getitemlink(Jellopy); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Jellopy, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000000eF'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000000eF&00(00(00(00(00"); + } else { + .@pass = (.@str$ == "000000eF&00'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_Headgear { + .@str$ = getitemlink(Hat, 0, 0, 0, 0); + .@str2$ = getitemlink(Hat); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Hat, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000481zO'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000481zO&0g(00(00(00(00"); + } else { + .@pass = (.@str$ == "000481zO&0g'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_BaseWeapon { + .@str$ = getitemlink(Knife, 0, 0, 0, 0); + .@str2$ = getitemlink(Knife); + + // Sanity check, optional parameters should not affect result + if (.@str$ != .@str2$) { + return false; + } + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Knife, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000021jn'00'00'00'00"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000021jn&00(00(00(00(00"); + } else { + .@pass = (.@str$ == "000021jn&00'00)00)00)00)00"); + } + + return .@pass; +} + +function script F_TestGetItemLink_FullWeapon { + setarray(.@cards[0], Fabre_Card, Captain_Felock_Card, 0, Agility1); + setarray(.@options[0], WEAPON_ATTR_GROUND, 1, 0, VAR_MAXHPAMOUNT, 10, 0); + .@str$ = getitemlink(Knife, 10, .@cards, .@options, 2); + + .@pass = false; + if (PACKETVER < 20150923) { + .@pass = (.@str$ == getiteminfo(Knife, ITEMINFO_NAME)); + } else if (PACKETVER < 20161116) { + .@pass = (.@str$ == "000021jn%0a'12y'74q'00'1ei)2R*00+01)01*00+0a"); + } else if (PACKETVER < 20200916) { + .@pass = (.@str$ == "000021jn%0a&00(12y(74q(00(1ei*2R+00,01*01+00,0a"); + } else { + .@pass = (.@str$ == "000021jn%0a&00'02)12y)74q)00)1ei+2R,00-01+01,00-0a"); + } + + return .@pass; +} + function script HerculesSelfTestHelper { if (.once > 0) return .errors; @@ -885,6 +973,11 @@ function script HerculesSelfTestHelper { callsub(OnCheck, "getcalendartime: next occurence of current Hour/Minute", F_TestCalendarNextTime(), true); + callsub(OnCheck, "getitemlink: etc tag", F_TestGetItemLink_Etc(), true); + callsub(OnCheck, "getitemlink: headgear tag", F_TestGetItemLink_Headgear(), true); + callsub(OnCheck, "getitemlink: basic weapon tag", F_TestGetItemLink_BaseWeapon(), true); + callsub(OnCheck, "getitemlink: complete weapon tag", F_TestGetItemLink_FullWeapon(), true); + if (.errors) { consolemes(CONSOLEMES_DEBUG, "Script engine self-test [ \033[0;31mFAILED\033[0m ]"); consolemes(CONSOLEMES_DEBUG, "**** The test was completed with " + .errors + " errors. ****"); diff --git a/src/map/script.c b/src/map/script.c index 1bc9ad969b7..a37fb0aa2d6 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -3646,7 +3646,7 @@ static void script_array_update(struct reg_db *src, int64 num, bool empty) /** * Given the array array_data, fetches the number value at index. - * + * * @param st executing script state * @param array_data script_data containing the array paramter to have an index fetched from * @param index index to be fetched @@ -3655,7 +3655,7 @@ static void script_array_update(struct reg_db *src, int64 num, bool empty) static int32 script_array_get_num_member(struct script_state *st, struct script_data *array_data, int index) { uint32 id = reference_getid(array_data); - + int32 value = (int32) h64BPTRSIZE(script->get_val2(st, reference_uid(id, index), reference_getref(array_data))); script_removetop(st, -1, 0); @@ -11924,7 +11924,7 @@ static BUILDIN(itemskill) int flag = script_hasdata(st, 4) ? script_getnum(st, 4) : ISF_NONE; sd->auto_cast_current.itemskill_check_conditions = ((flag & ISF_CHECKCONDITIONS) == ISF_CHECKCONDITIONS); - + bool cast_on_self = ((flag & ISF_CASTONSELF) == ISF_CASTONSELF); struct block_list *target = cast_on_self ? &sd->bl : NULL; if (sd->auto_cast_current.itemskill_check_conditions) { @@ -16396,6 +16396,108 @@ static BUILDIN(setiteminfo) return true; } +/** + * Creates a Item Link tag for the given item info. + * + * getitemlink({, {, {, {, }}}}) + * getitemlink(""{, {, {, {, }}}}) + */ +static BUILDIN(getitemlink) +{ + struct item_data *itd; + + if (script_isstringtype(st, 2)) { /// Item name. + const char *name = script_getstr(st, 2); + itd = itemdb->search_name(name); + + if (itd == NULL) + ShowError("%s: Non-existent item name \"%s\".\n", __func__, name); + } else { /// Item ID. + itd = itemdb->exists(script_getnum(st, 2)); + + if (itd == NULL) + ShowError("%s: Non-existent item id \"%d\".\n", __func__, script_getnum(st, 2)); + } + + if (itd == NULL) { + script_pushconststr(st, ""); + return false; + } + + struct item link_item = { 0 }; + link_item.nameid = itd->nameid; + link_item.refine = script_hasdata(st, 3) ? script_getnum(st, 3) : 0; + + // Cards + if (script_hasdata(st, 4)) { + struct script_data *data = script_getdata(st, 4); + const char *name = reference_getname(data); + + struct map_session_data *sd = NULL; + if (data_isreference(data) && is_int_variable(name)) { + if (not_server_variable(*name)) { + sd = script->rid2sd(st); + if (sd == NULL) + return true; // no player attached + } + + int array_size = script->array_highest_key(st, sd, name, reference_getref(data)); + array_size = cap_value(array_size, 0, MAX_SLOTS); + + for (int i = 0; i < array_size; ++i) + link_item.card[i] = script->array_get_num_member(st, data, i); + } else if (!data_isint(data) || script_getnum(st, 4) != 0) { + ShowError("%s: Invalid card list received. Card list must be 0 or an array of card IDs (number)\n", __func__); + script->reportdata(data); + script_pushconststr(st, ""); + return true; + } + } + + // Options + if (script_hasdata(st, 5)) { + struct script_data *data = script_getdata(st, 5); + const char *name = reference_getname(data); + + if (data_isreference(data) && is_int_variable(name)) { + struct map_session_data *sd = NULL; + if (not_server_variable(*name)) { + sd = script->rid2sd(st); + if (sd == NULL) + return true; // no player attached + } + + int array_size = script->array_highest_key(st, sd, reference_getname(data), reference_getref(data)); + array_size = cap_value(array_size, 0, MAX_ITEM_OPTIONS * 3); + + // arrays ending with 0 will have arraysize not divisible by 3, but acessing those indexes will result in 0 + for (int i = 0, j = 0; i < array_size; i += 3, ++j) { + link_item.option[j].index = script->array_get_num_member(st, data, i); + link_item.option[j].value = script->array_get_num_member(st, data, i + 1); + link_item.option[j].param = script->array_get_num_member(st, data, i + 2); + } + } else if (!data_isint(data) || script_getnum(st, 5) != 0) { + ShowError("%s: Invalid options list received. Option list must be 0 or an array of option data (number, number, number)\n", __func__); + script->reportdata(data); + script_pushconststr(st, ""); + return false; + } + } + + if (script_hasdata(st, 6)) + link_item.grade = script_getnum(st, 6); + + StringBuf buf; + StrBuf->Init(&buf); + + clif->format_itemlink(&buf, &link_item); + script_pushstrcopy(st, StrBuf->Value(&buf)); + + StrBuf->Destroy(&buf); + + return true; +} + /*========================================== * Returns value from equipped item slot n [Lupus] * getequpcardid(num,slot) @@ -28782,6 +28884,7 @@ static void script_parse_builtin(void) BUILDIN_DEF(strcmp,"ss"), BUILDIN_DEF(getiteminfo,"vi"), //[Lupus] returns Items Buy / sell Price, etc info BUILDIN_DEF(setiteminfo,"iii"), //[Lupus] set Items Buy / sell Price, etc info + BUILDIN_DEF(getitemlink, "v????"), BUILDIN_DEF(getequipcardid,"ii"), //[Lupus] returns CARD ID or other info from CARD slot N of equipped item BUILDIN_DEF(getequippedoptioninfo, "i"), BUILDIN_DEF(getequipoption, "iii"), diff --git a/src/map/script.h b/src/map/script.h index b3afbd0e7e3..4ed16e82a6f 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -176,6 +176,7 @@ struct item_data; #define script_getvaridx(var) ( (uint32)(int64)((var >> 32) & 0xFFFFFFFF) ) #define not_server_variable(prefix) ( (prefix) != '$' && (prefix) != '.' && (prefix) != '\'') +#define is_int_variable(name) ( (name)[strlen(name) - 1] != '$' ) #define is_string_variable(name) ( (name)[strlen(name) - 1] == '$' ) #define BUILDIN(x) bool buildin_ ## x (struct script_state* st)