gcr_fc
Silver Membergcr_fc ganhou no último dia 26 de Maio
gcr_fc teve o conteúdo mais curtida!
2
8 Seguidores
Sobre Mim
-
Discord
GuiCandiotto
Últimos Visitantes
2.679 visualizações
gcr_fc's Achievements
-
bacana, na verdade com os emuladores que tem disponivel aqui mesmo na WC isso é tranquilo de fazer... so pegar o cliente seja a versão que quiser e alinhar as structs como a struct_mob struct_score e etc com o cliente... no mais é so editar o serverlist do cliente e logar... é possivel fazer isso com o cliente atual do global tbm se quiser, porem precisa alinhar as structs, com o proprio sniffer que postei ai da pra acertar sizes e etc no global e nos outros clientes so mudar os offsets dos hooks e da pra capturar os packets tbm. o problema maior na verdade é que pra essas versõies ficarem viaveis precisa de um patch no cliente para poder atualizar o size das variaveis, pois sao bem limitadas. no mais a maioria das coisas ja funciona, precisa acertar os calculos de grid de inventario e etc, mas nada muito complicado, ainda mais com as IAs ai, pode extrair as funções BASE_ diretamente do binario do cliente, que ja vai ter a maior parte dos calculos, alinhados com o cliente. por exemplo, segue a BASE_CheckPacketSize do Global int BASE_CheckPacketSize(const unsigned short* pPacket) { if (!pPacket) return 0; struct PacketSizeEntry { unsigned short type; unsigned short expectedSize; }; // Opcode = base_number | flags. Flags: // G2C = 0x0100 (GAME2CLIENT) C2G = 0x0200 (CLIENT2GAME) // DB2G = 0x0400 (DB2GAME) G2DB = 0x0800 (GAME2DB) // DB2NP = 0x1000 NP2DB = 0x2000 // NEW = 0x4000 (custom/extended) // Sizes use Global 12-byte header (no PacketMAC). static const PacketSizeEntry table[] = { // ===================================================== // GAME2CLIENT (0x1xx) — Server → Client // ===================================================== { 0x0101, 140 }, // _MSG_MessagePanel (1|G2C) { 0x0102, 148 }, // _MSG_MessageBoxOk (2|G2C) { 0x0103, 20 }, // 3|G2C — unknown { 0x0104, 184 }, // 4|G2C — unknown { 0x010A, 2808 }, // _MSG_CNFAccountLogin (10|G2C) — SELCHAR+Cargo { 0x0110, 1224 }, // _MSG_CNFNewCharacter (16|G2C) — SELCHAR { 0x0112, 1224 }, // _MSG_CNFDeleteCharacter (18|G2C) — SELCHAR { 0x0114, 2104 }, // _MSG_CNFCharacterLogin (20|G2C) — full MOB+Score { 0x0116, 12 }, // _MSG_CNFCharacterLogout (22|G2C) — STANDARD { 0x0117, 12 }, // _MSG_NewAccountFail (23|G2C) — STANDARD { 0x0119, 12 }, // _MSG_CharacterLoginFail (25|G2C) — STANDARD { 0x011A, 12 }, // _MSG_NewCharacterFail (26|G2C) — STANDARD { 0x011B, 12 }, // _MSG_DeleteCharacterFail (27|G2C) — STANDARD { 0x011C, 12 }, // _MSG_AlreadyPlaying (28|G2C) — STANDARD { 0x011D, 12 }, // _MSG_StillPlaying (29|G2C) — STANDARD { 0x011E, 20 }, // 30|G2C — unknown (STANDARDPARM2?) { 0x0120, 12 }, // 32|G2C — unknown (STANDARD) { 0x0165, 16 }, // _MSG_RemoveMob (101|G2C) — STANDARDPARM { 0x016F, 16 }, // _MSG_DecayItem (111|G2C) — STANDARDPARM { 0x0171, 32 }, // _MSG_CNFGetItem (113|G2C) { 0x0175, 28 }, // _MSG_CNFDropItem (117|G2C) { 0x017C, 344 }, // _MSG_ShopList (124|G2C) — ShopType+Items+Tax { 0x0181, 28 }, // _MSG_SetHpMp (129|G2C) { 0x0182, 28 }, // _MSG_SendItem (130|G2C) { 0x0185, 784 }, // _MSG_UpdateCarry (133|G2C) — Carry[]+Coin { 0x018A, 20 }, // _MSG_SetHpDam (138|G2C) { 0x018B, 16 }, // _MSG_UpdateWeather (139|G2C) — STANDARDPARM { 0x018D, 12 }, // _MSG_ReqChallange (141|G2C) — STANDARD { 0x0193, 16 }, // _MSG_SetClan (147|G2C) — STANDARDPARM { 0x0194, 16 }, // 148|G2C — unknown (STANDARDPARM) { 0x01BF, 36 }, // 191|G2C — unknown { 0x01C1, 24 }, // 193|G2C — unknown (STANDARDPARM3?) // ===================================================== // CLIENT2GAME (0x2xx) — Client → Server // ===================================================== { 0x020C, 124 }, // 12|C2G — unknown { 0x020D, 124 }, // _MSG_AccountLogin (13|C2G) { 0x020E, 292 }, // 14|C2G — unknown (large packet) { 0x020F, 36 }, // _MSG_CreateCharacter (15|C2G) { 0x0211, 48 }, // _MSG_DeleteCharacter (17|C2G) { 0x0213, 36 }, // _MSG_CharacterLogin (19|C2G) { 0x021F, 16 }, // 31|C2G — unknown (STANDARDPARM) { 0x026E, 36 }, // _MSG_CreateItem (110|C2G) { 0x0270, 28 }, // _MSG_GetItem (112|C2G) { 0x0277, 20 }, // _MSG_ApplyBonus (119|C2G) { 0x027B, 16 }, // _MSG_REQShopList (123|C2G) — STANDARDPARM { 0x0289, 12 }, // _MSG_Restart (137|C2G) — STANDARD { 0x028B, 20 }, // _MSG_Quest (139|C2G) — STANDARDPARM2 { 0x028C, 16 }, // _MSG_Deprivate (140|C2G) — STANDARDPARM { 0x028E, 16 }, // _MSG_Challange (142|C2G) — STANDARDPARM { 0x028F, 20 }, // _MSG_ChallangeConfirm (143|C2G) — STANDARDPARM2 { 0x0290, 16 }, // _MSG_ReqTeleport (144|C2G) — STANDARDPARM { 0x0291, 16 }, // _MSG_ChangeCity (145|C2G) — STANDARDPARM { 0x0292, 16 }, // _MSG_SetHpMode (146|C2G) — STANDARDPARM { 0x0295, 160 }, // 149|C2G — unknown { 0x02BC, 140 }, // 188|C2G — unknown { 0x02BD, 24 }, // 189|C2G — unknown (STANDARDPARM3?) { 0x02BE, 20 }, // _MSG_DoJackpotBet (190|C2G) — STANDARDPARM2 { 0x02C2, 24 }, // 194|C2G — unknown (STANDARDPARM3?) { 0x02C3, 132 }, // _MSG_CombineItemLindy (195|C2G) { 0x02C4, 132 }, // _MSG_CombineItemShany (196|C2G) { 0x02C7, 16 }, // 199|C2G — unknown (STANDARDPARM) // ===================================================== // GAME2CLIENT | CLIENT2GAME (0x3xx) — Bidirectional // ===================================================== { 0x0333, 140 }, // _MSG_MessageChat (51|G2C|C2G) { 0x0334, 160 }, // _MSG_MessageWhisper (52|G2C|C2G) { 0x0336, 172 }, // _MSG_UpdateScore (54|G2C|C2G) { 0x0337, 48 }, // _MSG_UpdateEtc (55|G2C|C2G) { 0x0338, 32 }, // _MSG_CNFMobKill (56|G2C|C2G) { 0x0364, 256 }, // _MSG_CreateMob (100|G2C|C2G) { 0x0366, 52 }, // _MSG_Action2 (102|G2C|C2G) { 0x0367, 168 }, // _MSG_Attack (103|G2C|C2G) { 0x0369, 16 }, // _MSG_NoViewMob (105|G2C|C2G) — STANDARDPARM { 0x036A, 20 }, // _MSG_Motion (106|G2C|C2G) { 0x036B, 88 }, // _MSG_UpdateEquip (107|G2C|C2G) { 0x036C, 52 }, // _MSG_Action (108|G2C|C2G) { 0x0373, 36 }, // _MSG_UseItem (115|G2C|C2G) { 0x0374, 20 }, // _MSG_UpdateItem (116|G2C|C2G) { 0x0376, 20 }, // _MSG_SwapItem (118|G2C|C2G) { 0x0378, 32 }, // _MSG_SetShortSkill (120|G2C|C2G) { 0x0379, 24 }, // _MSG_Buy (121|G2C|C2G) { 0x037A, 20 }, // _MSG_Sell (122|G2C|C2G) { 0x037D, 40 }, // _MSG_CNFAddParty (125|G2C|C2G) { 0x037E, 16 }, // _MSG_RemoveParty (126|G2C|C2G) { 0x037F, 44 }, // _MSG_SendReqParty (127|G2C|C2G) { 0x0380, 16 }, // 128|G2C|C2G — unknown (STANDARDPARM) { 0x0383, 216 }, // _MSG_Trade (131|G2C|C2G) { 0x0384, 12 }, // _MSG_QuitTrade (132|G2C|C2G) — STANDARD { 0x0386, 12 }, // _MSG_CNFCheck (134|G2C|C2G) — STANDARD { 0x0387, 16 }, // _MSG_Withdraw (135|G2C|C2G) — STANDARDPARM { 0x0388, 16 }, // _MSG_Deposit (136|G2C|C2G) — STANDARDPARM { 0x0397, 244 }, // _MSG_SendAutoTrade (151|G2C|C2G) { 0x039A, 16 }, // _MSG_ReqTradeList (154|G2C|C2G) — STANDARDPARM { 0x03A6, 132 }, // _MSG_CombineItem (166|G2C|C2G) { 0x03C0, 132 }, // _MSG_CombineItemTiny (192|G2C|C2G) { 0x03C9, 52 }, // _MSG_UseItem2 (201|G2C|C2G) { 0x03CA, 36 }, // 202|G2C|C2G — unknown { 0x03EA, 40 }, // 234|G2C|C2G — unknown // ===================================================== // DB2GAME (0x4xx) — DBSrv → TMSrv // ===================================================== { 0x0401, 140 }, // _MSG_DBMessagePanel (1|DB2G) { 0x0402, 148 }, // _MSG_DBMessageBoxOk (2|DB2G) { 0x0403, 184 }, // 3|DB2G — unknown { 0x0404, 12 }, // 4|DB2G — unknown (STANDARD) { 0x040A, 32 }, // _MSG_DBSavingQuit (10|DB2G) { 0x040B, 12 }, // _MSG_DBCNFAccountLogOut (11|DB2G) — STANDARD { 0x0416, 2808 }, // _MSG_DBCNFAccountLogin (22|DB2G) — SELCHAR+Cargo { 0x0417, 2104 }, // _MSG_DBCNFCharacterLogin (23|DB2G) — full MOB { 0x0418, 1224 }, // _MSG_DBCNFNewCharacter (24|DB2G) — SELCHAR { 0x0419, 1224 }, // _MSG_DBCNFDeleteCharacter (25|DB2G) — SELCHAR { 0x041A, 12 }, // _MSG_DBNewAccountFail (26|DB2G) — STANDARD { 0x041B, 12 }, // 27|DB2G — unknown (STANDARD, gap in TMProject) { 0x041C, 12 }, // _MSG_DBCharacterLoginFail (28|DB2G) — STANDARD { 0x041D, 12 }, // _MSG_DBNewCharacterFail (29|DB2G) — STANDARD { 0x041E, 12 }, // _MSG_DBDeleteCharacterFail (30|DB2G) — STANDARD { 0x041F, 12 }, // _MSG_DBAlreadyPlaying (31|DB2G) — STANDARD { 0x0420, 12 }, // _MSG_DBStillPlaying (32|DB2G) — STANDARD { 0x0421, 12 }, // _MSG_DBAccountLoginFail_Acc (33|DB2G) — STANDARD { 0x0422, 12 }, // _MSG_DBAccountLoginFail_Pas (34|DB2G) — STANDARD { 0x0423, 24 }, // _MSG_DBSetIndex (35|DB2G) — STANDARDPARM3 { 0x0424, 12 }, // _MSG_DBAccountLoginFail_Blk (36|DB2G) — STANDARD { 0x0425, 12 }, // _MSG_DBAccountLoginFail_Dis (37|DB2G) — STANDARD { 0x0439, 20 }, // 57|DB2G — unknown (STANDARDPARM2?) { 0x043B, 12 }, // 59|DB2G — unknown (STANDARD) { 0x043D, 140 }, // 61|DB2G — unknown { 0x043E, 12 }, // 62|DB2G — unknown (STANDARD) // ===================================================== // Multi-flag ranges // ===================================================== { 0x07BE, 180 }, // 190|G2C|C2G|DB2G — unknown { 0x0801, 292 }, // _MSG_DBNewAccount (1|G2DB) — MSG_NewAccount { 0x0802, 36 }, // _MSG_DBCreateCharacter (2|G2DB) { 0x0803, 124 }, // _MSG_DBAccountLogin (3|G2DB) { 0x0804, 36 }, // _MSG_DBCharacterLogin (4|G2DB) { 0x0805, 12 }, // _MSG_DBNoNeedSave (5|G2DB) — STANDARD { 0x0806, 3648 }, // _MSG_SavingQuit (6|G2DB) — full MOB save { 0x0807, 3648 }, // _MSG_DBSaveMob (7|G2DB) — full MOB save { 0x0809, 48 }, // _MSG_DBDeleteCharacter (9|G2DB) { 0x0BBF, 180 }, // 191|G2C|C2G|G2DB — unknown { 0x0E0E, 20 }, // _MSG_War (14|C2G|DB2G|G2DB) — STANDARDPARM2 { 0x0FAA, 52 }, // _MSG_ReqTransper (170|G2C|C2G|DB2G|G2DB) { 0x0FBC, 180 }, // 188|G2C|C2G|DB2G|G2DB — unknown // ===================================================== // NP (DB2NP / NP2DB) ranges // ===================================================== { 0x1001, 48 }, // _MSG_NPReqIDPASS (1|DB2NP) { 0x1004, 12 }, // _MSG_NPNotFound (4|DB2NP) — STANDARD { 0x1005, 10040 }, // _MSG_NPAccountInfo (5|DB2NP) { 0x13BD, 180 }, // 189|G2C|C2G|DB2NP — unknown { 0x2002, 48 }, // _MSG_NPIDPASS (2|NP2DB) { 0x2003, 44 }, // _MSG_NPReqAccount (3|NP2DB) { 0x2006, 10040 }, // _MSG_NPReqSaveAccount (6|NP2DB) — MSG_NPAccountInfo { 0x3007, 28 }, // _MSG_NPDisable (7|DB2NP|NP2DB) { 0x3008, 28 }, // _MSG_NPEnable (8|DB2NP|NP2DB) { 0x300A, 16 }, // _MSG_NPState (10|DB2NP|NP2DB) — STANDARDPARM { 0x3409, 164 }, // _MSG_NPNotice (9|DB2NP|NP2DB|DB2G) { 0x3C0F, 492 }, // 15|DB2NP|NP2DB|DB2G|G2DB — unknown (extended DBSendItem?) }; static const int TABLE_SIZE = sizeof(table) / sizeof(table[0]); unsigned int packetType = pPacket[2]; unsigned short packetSize = pPacket[0]; for (int i = 0; i < TABLE_SIZE; ++i) { if (table[i].type == packetType) { if (packetSize != table[i].expectedSize) { char Buffer[256]{}; // S140 (M2): sprintf_s — buffer 256B é mais que suficiente, // mas modernizado para consistência com resto do código. sprintf_s(Buffer, sizeof(Buffer), "sys, Type check missing type:%u size:%u", packetType, packetSize); // nullsub_11(Buffer, "-sys", 0); // log function stubbed no exe return 1; } return 0; } } return 0; } tem inclusive alguns opcodes novos. nas prints ai por exemplo é a DBSrv que descompilei da DBSRVIN e como da pra ver a TMSRVIN esta conectada nela, a DBSrv esta 100% funcional, com todos os sitemas de importitem updateuser criação de arch/celestial/hardcore tudo funcional, com alguns pequenos bugs corrigidos. E tbm foi reconstruido o NPKO usando todos os packets originais, logando na DBSrv enviando msg ingame global e direcionada, assim como desbilitação da conta para poder editar/analisar, com o implementação extra de desconectar automaticamente a conta quando ela estiver logada e for desabilitada no NPKO, isso nao existia no original.
- 37 respostas
-
- 10
-
-
-
Tm project mesmo, só acertei as structs dos packets diferentes Mais creio que consegue conectar com qualquer 759, até mesmo aquele do br com xtrap, só patchar ele
-
IDA Free Vtable Analyzer 5.3 - Script IDC
gcr_fc respondeu ao tópico de gcr_fc em WYD - Desenvolvimento
como assim? é um codigo so selecionar, copiar e colar... -
gcr_fc começou a seguir Cade os old school em fórum? , Versões oficiais Vasadas. , IDA Free Vtable Analyzer 5.3 - Script IDC e 1 outro
-
Fala pessoal blz, estou descompilando o sistema de painéis do WYD Global e pra facilitar minha vida criei esse script .idc que faz a analise completa e extrai diversas informações digamos interessantes do cliente, o que facilita bem a vida. Como fiz para o WYD trabalha só com o exes compilados em MSVC, mas pode facilmente ser ajustado para trabalhar com outros compiladores e tbm outros exes. No próprio script tem a descrição das opções de configuração. Para usarem, salvem como XXXXX.idc, basta carregar o exe no IDA Shift+F2 importar o script ou colar o conteúdo no editor de script e rodar. A analise pode demorar a depender do tamanho do exe analisado e do seu pc tbm. Seguem alguns prints. bom é isso... /* * ============================================================================ * Vtable Analyzer Pro v5.3 - By Guilherme Candiotto * MSVC RTTI / vtable harvester for IDA Free 9.x and IDA Pro 7.x+ * ============================================================================ * * WHAT IT DOES * ------------ * Detects C++ classes in MSVC binaries via RTTI (CompleteObjectLocator), * reconstructs the inheritance hierarchy (including multi-inheritance via * secondary vtables), names virtual methods, applies __thiscall + this * typing for Hex-Rays, infers sizeof, detects singletons, annotates * virtual call sites, and emits a C++ header with the recovered classes. * * HOW TO USE * ---------- * File > Script file... -> select this .idc * * Outputs in <OUTPUT_DIR>: * vtables_analysis.json structured class data (parents, methods, ...) * vtables_analysis.log plain-text run log * vtables_overrides.json override relationships (child slot -> parent) * wyd_classes.h C++ header with reconstructed classes * * Re-run with Alt+F5 (configurable via REGISTER_HOTKEY). * * REQUIREMENTS * ------------ * IDA Free 9.x or IDA Pro >= 7.x. * MSVC binary with RTTI enabled (CompleteObjectLocator present). * Tested on x86; x64 supported via BRUTE_FORCE_X64_RVA path. * * CONFIG * ------ * See #defines below. Each one is documented at its declaration. * * License: MIT. Use freely; attribution appreciated. * ============================================================================ */ #include <idc.idc> // ---------------------- CONFIG ---------------------- // Pasta onde gravar os arquivos de saida. // "" = mesma pasta do .idb (recomendado). // "D:/path" = caminho absoluto. Use / ou \\ no Windows. #define OUTPUT_DIR "" #define OUTPUT_JSON_NAME "vtables_analysis.json" // structured class data (parents, methods, sizeof, ...) #define OUTPUT_LOG_NAME "vtables_analysis.log" // plain-text run log (progress + warnings) #define OUTPUT_OVERRIDES_NAME "vtables_overrides.json" // override map: child slot -> parent function #define OUTPUT_HEADER_NAME "wyd_classes.h" // C++ header with reconstructed classes (importable in IDA Local Types) #define DRY_RUN 0 // 1 = preview only (no IDB modification, no file writes) #define CREATE_STRUCTS 1 // 1 = create opaque <Class>_t struct in IDA (vptr at offset 0) #define RENAME_OVERRIDES 0 // 1 = rename even functions with existing custom names (e.g. from PDB); 0 = preserve them #define SCAN_DATA_TOO 1 // 1 = scan .data segment too (some binaries have vtables there, not just .rdata) #define STRING_FIRST_SCAN 1 // 1 = scan via RTTI strings (.?AV/.?AU), reconstruct TD/COL/vtable from xrefs #define POINTER_FIRST_SCAN 1 // 1 = sequential DWORD scan in .rdata looking for TD->COL pointer pattern (classic mode) #define PROPAGATE_OVERRIDES 1 // 1 = final pass detecting overrides (child slot != parent func) vs inherited methods #define BRUTE_FORCE_X64_RVA 1 // so executa em x64; inerte em x86 #define BRUTE_FORCE_X86 1 // varredura simetrica para x86 (sig=0) #define APPLY_THISCALL_TYPES 1 // aplica __thiscall(_t *this) nos vmethods #define DETECT_OBJECT_SIZES 1 // tenta deduzir sizeof via operator new #define DETECT_SINGLETONS 1 // marca globais que recebem instancias #define ANNOTATE_VCALLS 1 // comenta call [reg+N] com possibilidades #define EMIT_CPP_HEADER 1 // emite wyd_classes.h #define VCALL_MAX_METHODS 4 // max metodos listados por slot #define MAX_VTABLE_METHODS 2048 // safety cap: vtables larger than this are rejected (likely false positives) #define MIN_VTABLE_METHODS 1 // minimum slots to consider a vtable valid // v5.2 ---- #define CLEAR_OUTPUT_ON_START 1 // limpa Output do IDA no inicio #define REGISTER_HOTKEY 1 // Alt+F5 re-roda o script #define HOTKEY_COMBO "Alt-F5" // hotkey to re-run the script (IDA hotkey syntax: "Mod-Key") #define PROGRESS_EVERY 4096 // emite msg() a cada N iteracoes // v5.3: filtro de classes framework (MFC/STL/ATL/etc) #define EXCLUDE_FRAMEWORK_CLASSES 1 // 1 = pula MFC, STL, ATL, Gdiplus... // v5.3: detectores melhorados #define SIZE_INFER_FROM_BODY 1 // estima sizeof pelo max [this+N] no ctor #define SIZE_BODY_SCAN_LIMIT 80 // max instrucoes a olhar no body do ctor #define SINGLETON_AGGRESSIVE 1 // tenta padroes adicionais alem do basico // v5.3: limite de varredura para string-first scan (evita travar em // binarios de servidor com .data gigante (>100MB de tabelas de jogo). // 0 = sem limite. Default: 50MB. #define STRING_SCAN_MAX_SEG_SIZE 0x3200000 // 50MB // Cores BGR usadas pra colorir cada tipo de item no Disassembly View do IDA. // Formato: 0xBBGGRR. Mude pra 0 pra desabilitar coloracao especifica. #define COLOR_VTABLE_SLOT 0xFFE8E0 #define COLOR_VFUNC 0xE0FFE0 #define COLOR_PURE 0xC0C0C0 #define COLOR_DTOR 0xC0C0FF #define COLOR_CTOR 0xC0FFFF #define COLOR_RTTI_COL 0xE0E0FF #define COLOR_TYPE_DESC 0xFFE0FF #define COLOR_OVERRIDE 0xE0FFFF // Nomes de arrays internos do IDB (mantem cache entre runs do script). // Mude o sufixo (_v5_/_v4_) se quiser invalidar o cache forcando re-scan. #define ARR_VT_SEEN "vap_v4_vt_seen" #define ARR_TD2VT "vap_v4_td2vt" #define ARR_VT2TD "vap_v4_vt2td" #define ARR_VT2COL "vap_v4_vt2col" #define ARR_VT2COUNT "vap_v4_vt2count" #define ARR_VT2NAME "vap_v4_vt2name" #define ARR_VT2PARENTS "vap_v4_vt2parents" #define ARR_VT_LIST "vap_v4_vt_list" #define ARR_VT2OFFSET "vap_v5_vt2offset" // COL.offset (0=primary) #define ARR_VT2SIZE "vap_v5_vt2size" #define ARR_VT2SINGLETON "vap_v5_vt2singleton" #define ARR_SLOT_METHODS "vap_v5_slot_methods" #define ARR_HDR_EMITTED "vap_v5_hdr_emitted" #define ARR_TOPO_VISITED "vap_v5_topo_visited" #define ARR_TOPO_ORDER "vap_v5_topo_order" #define ARR_TOPO_COUNT "vap_v5_topo_count" // ---------------------- GLOBAIS ---------------------- // FIX IDC 9.x: variaveis globais usam "extern", nao "static" (que eh // reservado pra funcoes). Senao o parser cuspia "Missing brace". extern g_ptr_size; extern g_is_64; extern g_imagebase; extern g_log_handle; extern g_json_handle; extern g_overrides_handle; extern g_json_first; extern g_overrides_first; extern g_vt_list_count; extern g_stat_classes; extern g_stat_methods; extern g_stat_renamed; extern g_stat_ctors; extern g_stat_dtors; extern g_stat_pures; extern g_stat_overrides; extern g_stat_inherited; extern g_stat_brute; extern g_stat_thiscall; extern g_stat_sizes; extern g_stat_singletons; extern g_stat_vcalls; extern g_stat_header_classes; extern g_stat_renamed_inherited; extern g_stat_errors; extern g_purecall_csv; extern g_stat_filtered; // ========================================================================= // user_cancelled() - cooperative cancel hook (IDC stub) // ========================================================================= // IDC 9.x removed the wait_box / user_cancelled API; only IDAPython still // exposes it (idaapi.user_cancelled / ida_kernwin.user_cancelled). // // This stub always returns 0, so the if (user_cancelled()) checks scattered // across the heavy scan loops below are dead code - a long run cannot be // aborted gracefully from IDC (you have to kill the IDA process). // // TO ENABLE REAL CANCEL SUPPORT (IDAPython port): // 1. Wrap this script as an IDAPython plugin (.py instead of .idc). // 2. Replace this stub with: // import ida_kernwin // def user_cancelled(): // return ida_kernwin.user_cancelled() // 3. Bracket the main() entry with: // ida_kernwin.show_wait_box("HIDECANCEL\nVtable Analyzer: running...") // try: // main() // finally: // ida_kernwin.hide_wait_box() // 4. The 10 existing `if (user_cancelled()) return;` calls in this script // will then start working as cooperative cancel points. // // Until then, this stub keeps the IDC source compatible and side-effect-free. // ========================================================================= static user_cancelled() { return 0; } // ========================================================================= // LOG // ========================================================================= static log_line(level, line) { auto stamp = "[" + level + "] " + line; msg("%s\n", stamp); if (g_log_handle != 0) fprintf(g_log_handle, "%s\n", stamp); } static log_info(line) { log_line("INFO", line); } static log_warn(line) { log_line("WARN", line); } static log_error(line) { log_line("ERR ", line); g_stat_errors = g_stat_errors + 1; } // ========================================================================= // PATH HELPERS // ========================================================================= static find_last_char(s, c) { if (s == 0 || s == "") return -1; auto i; auto last = -1; auto n = strlen(s); for (i = 0; i < n; i = i + 1) { if (substr(s, i, i + 1) == c) last = i; } return last; } static dirname_of(path) { if (path == 0 || path == "") return ""; auto p1 = find_last_char(path, "\\"); auto p2 = find_last_char(path, "/"); auto pos = (p1 > p2) ? p1 : p2; if (pos == -1) return ""; return substr(path, 0, pos); } static normalize_path(p) { if (p == 0 || p == "") return ""; auto out = ""; auto i; auto n = strlen(p); for (i = 0; i < n; i = i + 1) { auto c = substr(p, i, i + 1); if (c == "\\") out = out + "/"; else out = out + c; } return out; } static path_join(dir, name) { if (dir == "" || dir == 0) return name; auto d = normalize_path(dir); auto last = substr(d, strlen(d) - 1, strlen(d)); if (last == "/") return d + name; return d + "/" + name; } static resolve_output_dir() { if (OUTPUT_DIR != "" && OUTPUT_DIR != 0) return normalize_path(OUTPUT_DIR); auto idb = get_idb_path(); auto d = dirname_of(idb); if (d == "" || d == 0) return "."; return normalize_path(d); } static safe_fopen(path, mode) { auto h = fopen(path, mode); if (h == 0) { msg("[ERR ] FOPEN FALHOU: '%s' (modo '%s')\n", path, mode); msg(" -> Verifique se a pasta existe e tem permissao de escrita.\n"); msg(" -> Ajuste OUTPUT_DIR no topo do script se necessario.\n"); return 0; } msg("[INFO] arquivo aberto OK: %s\n", path); return h; } // ========================================================================= // HELPERS BASICOS // ========================================================================= static read_ptr(ea) { return g_is_64 ? get_qword(ea) : get_dword(ea); } static rva_to_ea(rva) { return g_imagebase + rva; } static seg_is_executable(ea) { if (!is_loaded(ea)) return 0; return (get_segm_attr(ea, SEGATTR_PERM) & SEGPERM_EXEC) != 0; } static seg_name_of(ea) { if (!is_loaded(ea)) return ""; return get_segm_name(ea); } static is_valid_code_ptr(ea) { if (ea == BADADDR || ea == 0) return 0; if (!is_loaded(ea)) return 0; return seg_is_executable(ea); } static is_valid_data_ptr(ea) { if (ea == BADADDR || ea == 0) return 0; return is_loaded(ea); } static starts_with(s, prefix) { if (s == 0 || prefix == 0) return 0; if (strlen(s) < strlen(prefix)) return 0; return substr(s, 0, strlen(prefix)) == prefix; } static ends_with(s, suffix) { if (s == 0 || suffix == 0) return 0; auto sl = strlen(s); auto pl = strlen(suffix); if (sl < pl) return 0; return substr(s, sl - pl, sl) == suffix; } static sanitize_name(s) { if (s == 0 || s == "") return "anon"; auto out = ""; auto i; auto n = strlen(s); for (i = 0; i < n; i = i + 1) { auto c = substr(s, i, i + 1); if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || (c >= "0" && c <= "9") || c == "_") { out = out + c; } else if (c == ":" || c == "<" || c == ">" || c == "," || c == " " || c == "*" || c == "&" || c == "(" || c == ")" || c == "[" || c == "]" || c == "-" || c == "." || c == "+" || c == "/" || c == "\\" || c == "$" || c == "@" || c == "?") { out = out + "_"; } } if (out == "") out = "anon"; return out; } // ========================================================================= // JSON ESCAPING // ========================================================================= static json_escape(s) { if (s == 0 || s == "") return ""; auto out = ""; auto i; auto n = strlen(s); for (i = 0; i < n; i = i + 1) { auto c = substr(s, i, i + 1); if (c == "\"") out = out + "\\\""; else if (c == "\\") out = out + "\\\\"; else if (c == "\n") out = out + "\\n"; else if (c == "\r") out = out + "\\r"; else if (c == "\t") out = out + "\\t"; else out = out + c; } return out; } // ========================================================================= // PURECALL ADDRESS LOOKUP (cache de enderecos conhecidos via imports) // ========================================================================= static csv_contains_long(csv, val) { if (csv == "" || csv == 0) return 0; auto buf = ""; auto i; auto n = strlen(csv); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(csv, i, i + 1) : ","; if (c == ",") { if (buf != "" && atol(buf) == val) return 1; buf = ""; } else { buf = buf + c; } } return 0; } static csv_append_long(csv, val) { if (csv == "") return ltoa(val, 10); return csv + "," + ltoa(val, 10); } static init_purecall_addresses() { g_purecall_csv = ""; auto names_buf = "__purecall|_purecall|purecall|__cxa_pure_virtual|_cxa_pure_virtual"; auto buf = ""; auto i; auto n = strlen(names_buf); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(names_buf, i, i + 1) : "|"; if (c == "|") { if (buf != "") { auto ea = get_name_ea_simple(buf); if (ea != BADADDR && !csv_contains_long(g_purecall_csv, ea)) { g_purecall_csv = csv_append_long(g_purecall_csv, ea); } } buf = ""; } else { buf = buf + c; } } } static is_known_purecall_addr(ea) { return csv_contains_long(g_purecall_csv, ea); } // ========================================================================= // PARSER MANUAL DO NOME MANGLED (com tratamento de templates) // // .?AV<Classe>@<NS_inner>@<NS_outer>@@ // .?AU<Struct>@<NS_inner>@<NS_outer>@@ // // Resultado em C++ pseudo: NS_outer::NS_inner::Classe // Templates "?$Nome@..." viram "tpl_Nome" para nao quebrar o sanitize. // ========================================================================= static cleanup_template_segment(seg) { if (!starts_with(seg, "?$")) return seg; auto rest = substr(seg, 2, strlen(seg)); auto pos = strstr(rest, "@"); auto name; if (pos != -1) name = substr(rest, 0, pos); else name = rest; if (name == "") name = "tpl"; return "tpl_" + name; } static manual_parse_rtti_name(mangled) { if (mangled == 0 || mangled == "") return ""; if (strlen(mangled) < 6) return ""; auto prefix = substr(mangled, 0, 4); if (prefix != ".?AV" && prefix != ".?AU") return ""; auto rest = substr(mangled, 4, strlen(mangled)); if (!ends_with(rest, "@@")) return ""; rest = substr(rest, 0, strlen(rest) - 2); if (rest == "") return ""; auto i; auto n = strlen(rest); auto cur = ""; auto result = ""; for (i = 0; i < n; i = i + 1) { auto c = substr(rest, i, i + 1); if (c == "@") { if (cur != "") { auto seg = cleanup_template_segment(cur); if (result == "") result = seg; else result = seg + "::" + result; } cur = ""; } else { cur = cur + c; } } if (cur != "") { auto seg2 = cleanup_template_segment(cur); if (result == "") result = seg2; else result = seg2 + "::" + result; } return result; } // ========================================================================= // v5.2: leitor de string C RAW (le bytes ate \0 sem depender do IDA // ter definido a string). Substitui o que create_strlit fazia, agora // que essa funcao mudou de assinatura no 9.x e nao podemos mais usar. // ========================================================================= static read_c_string_raw(ea) { if (!is_loaded(ea)) return ""; auto out = ""; auto i = 0; auto max_len = 512; while (i < max_len) { auto b = get_wide_byte(ea + i); if (b == 0) break; if (b < 0x20 || b > 0x7E) break; out = out + sprintf("%c", b); i = i + 1; } return out; } // ========================================================================= // RTTI - CompleteObjectLocator (COL) // +0x00 signature (0 = x86, 1 = x64) // +0x04 offset // +0x08 cdOffset // +0x0C pTypeDescriptor (RVA em x64, ptr em x86) // +0x10 pClassDescriptor(RVA em x64, ptr em x86) // +0x14 pSelf (so x64, RVA) // ========================================================================= static col_read_field(col_ea, off) { auto v = get_wide_dword(col_ea + off); if (g_is_64) return rva_to_ea(v); return v; } static validate_col(col_ea) { if (!is_valid_data_ptr(col_ea)) return 0; auto sig = get_wide_dword(col_ea); auto expected = g_is_64 ? 1 : 0; if (sig != expected) return 0; auto offset = get_wide_dword(col_ea + 0x04); auto cdOffset = get_wide_dword(col_ea + 0x08); if (offset > 0x10000) return 0; if (cdOffset > 0x10000) return 0; auto type_desc = col_read_field(col_ea, 0x0C); if (!is_valid_data_ptr(type_desc)) return 0; auto chd = col_read_field(col_ea, 0x10); if (!is_valid_data_ptr(chd)) return 0; auto chd_sig = get_wide_dword(chd); if (chd_sig != 0) return 0; auto nb = get_wide_dword(chd + 0x08); if (nb == 0 || nb > 256) return 0; if (g_is_64) { auto self_rva = get_wide_dword(col_ea + 0x14); if (rva_to_ea(self_rva) != col_ea) return 0; } return 1; } static read_typedesc_name(td_ea) { if (!is_valid_data_ptr(td_ea)) return ""; auto name_ea = td_ea + (g_ptr_size * 2); auto mangled = get_strlit_contents(name_ea, -1, STRTYPE_C); if (mangled == 0 || mangled == "") { mangled = read_c_string_raw(name_ea); } if (mangled == 0 || mangled == "") return ""; return mangled; } static demangle_class_name(mangled) { if (mangled == 0 || mangled == "") return ""; if (!starts_with(mangled, ".?AV") && !starts_with(mangled, ".?AU")) return ""; auto wrapped = "??_R0" + substr(mangled, 1, strlen(mangled)) + "@8"; auto dem = demangle_name(wrapped, INF_LONG_DN); if (dem != 0 && dem != "") { if (starts_with(dem, "class ")) dem = substr(dem, 6, strlen(dem)); if (starts_with(dem, "struct ")) dem = substr(dem, 7, strlen(dem)); auto suf = " `RTTI Type Descriptor'"; if (ends_with(dem, suf)) dem = substr(dem, 0, strlen(dem) - strlen(suf)); if (dem != "") return dem; } auto man = manual_parse_rtti_name(mangled); if (man != "") return man; return mangled; } // ========================================================================= // CHD + BaseClassArray -> lista de pais (TD addresses CSV) // // CHD: // +0x00 signature (==0) // +0x04 attributes // +0x08 numBaseClasses // +0x0C pBaseClassArray (RVA x64 / ptr x86) // // BCD (cada entrada da BCA aponta pra um BCD): // +0x00 pTypeDescriptor // +0x04 numContainedBases // +0x08 PMD (mdisp, pdisp, vdisp - 12 bytes) // +0x14 attributes // +0x18 pClassDescriptor // // BCA[0] eh sempre a propria classe; pulamos. // ========================================================================= static parse_base_classes(col_ea) { auto chd = col_read_field(col_ea, 0x10); if (!is_valid_data_ptr(chd)) return ""; auto sig = get_wide_dword(chd); if (sig != 0) return ""; auto num_bases = get_wide_dword(chd + 0x08); if (num_bases <= 1 || num_bases > 64) return ""; auto bca_field = get_wide_dword(chd + 0x0C); auto bca_ea = g_is_64 ? rva_to_ea(bca_field) : bca_field; if (!is_valid_data_ptr(bca_ea)) return ""; auto out = ""; auto i; for (i = 1; i < num_bases; i = i + 1) { auto bcd_field = get_wide_dword(bca_ea + i * 4); auto bcd_ea = g_is_64 ? rva_to_ea(bcd_field) : bcd_field; if (!is_valid_data_ptr(bcd_ea)) continue; auto td_field = get_wide_dword(bcd_ea); auto td_ea = g_is_64 ? rva_to_ea(td_field) : td_field; if (!is_valid_data_ptr(td_ea)) continue; if (out == "") out = ltoa(td_ea, 10); else out = out + "," + ltoa(td_ea, 10); } return out; } static parents_csv_to_names(csv) { if (csv == "" || csv == 0) return ""; auto out = ""; auto buf = ""; auto i; auto n = strlen(csv); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(csv, i, i + 1) : ","; if (c == ",") { if (buf != "") { auto td = atol(buf); auto mang = read_typedesc_name(td); auto nm = (mang != "") ? demangle_class_name(mang) : "??"; if (out == "") out = nm; else out = out + ", " + nm; } buf = ""; } else { buf = buf + c; } } return out; } // ========================================================================= // CACHE + HIERARQUIA (arrays do IDB) // ========================================================================= static arr_init(name) { auto id = get_array_id(name); if (id != -1) delete_array(id); create_array(name); } static arr_set_long(name, key, val) { auto id = get_array_id(name); if (id == -1) return; set_array_long(id, key, val); } static arr_get_long(name, key) { auto id = get_array_id(name); if (id == -1) return -1; return get_array_element(AR_LONG, id, key); } static arr_set_str(name, key, val) { auto id = get_array_id(name); if (id == -1) return; set_array_string(id, key, val); } static arr_get_str(name, key) { auto id = get_array_id(name); if (id == -1) return ""; auto v = get_array_element(AR_STR, id, key); if (v == 0 || v == -1) return ""; return v; } static cache_init() { arr_init(ARR_VT_SEEN); arr_init(ARR_TD2VT); arr_init(ARR_VT2TD); arr_init(ARR_VT2COL); arr_init(ARR_VT2COUNT); arr_init(ARR_VT2NAME); arr_init(ARR_VT2PARENTS); arr_init(ARR_VT_LIST); arr_init(ARR_VT2SIZE); arr_init(ARR_VT2SINGLETON); arr_init(ARR_VT2OFFSET); g_vt_list_count = 0; } static cache_seen(ea) { return arr_get_long(ARR_VT_SEEN, ea) > 0; } static cache_mark(ea) { arr_set_long(ARR_VT_SEEN, ea, 1); } static hier_register(td_ea, vt_ea, col_ea, sname, count, parents_csv, col_offset) { arr_set_long(ARR_VT2TD, vt_ea, td_ea); arr_set_long(ARR_VT2COL, vt_ea, col_ea); arr_set_long(ARR_VT2COUNT, vt_ea, count); arr_set_long(ARR_VT2OFFSET, vt_ea, col_offset); arr_set_str (ARR_VT2NAME, vt_ea, sname); arr_set_str (ARR_VT2PARENTS,vt_ea, parents_csv); arr_set_long(ARR_VT_LIST, g_vt_list_count, vt_ea); g_vt_list_count = g_vt_list_count + 1; if (col_offset == 0) { if (arr_get_long(ARR_TD2VT, td_ea) <= 0) { arr_set_long(ARR_TD2VT, td_ea, vt_ea); } } } static hier_offset(vt_ea) { auto v = arr_get_long(ARR_VT2OFFSET, vt_ea); if (v == -1) return 0; return v; } static hier_col(vt_ea) { auto v = arr_get_long(ARR_VT2COL, vt_ea); if (v == -1) return BADADDR; return v; } static hier_vt_by_td(td_ea) { auto v = arr_get_long(ARR_TD2VT, td_ea); if (v == -1) return BADADDR; return v; } static hier_count(vt_ea) { auto v = arr_get_long(ARR_VT2COUNT, vt_ea); if (v == -1) return 0; return v; } static hier_name(vt_ea) { return arr_get_str(ARR_VT2NAME, vt_ea); } static hier_parents(vt_ea) { return arr_get_str(ARR_VT2PARENTS, vt_ea); } static hier_vt_at(idx) { auto v = arr_get_long(ARR_VT_LIST, idx); if (v == -1) return BADADDR; return v; } // ========================================================================= // PURECALL // ========================================================================= static is_pure_call(faddr) { if (faddr == BADADDR || faddr == 0) return 0; if (is_known_purecall_addr(faddr)) return 1; auto name = get_func_name(faddr); if (name != 0 && name != "") { if (strstr(name, "purecall") != -1) return 1; if (strstr(name, "pure_virtual") != -1) return 1; if (strstr(name, "cxa_pure_virtual") != -1) return 1; } auto fend = get_func_attr(faddr, FUNCATTR_END); if (fend == BADADDR) return 0; if ((fend - faddr) > 24) return 0; auto mnem = print_insn_mnem(faddr); if (mnem == "int" || mnem == "hlt" || mnem == "ud2") return 1; if (mnem == "jmp") { auto dref = get_first_dref_from(faddr); while (dref != BADADDR) { auto target_via_iat = get_wide_dword(dref); if (g_is_64) target_via_iat = get_qword(dref); if (is_known_purecall_addr(target_via_iat)) return 1; auto dn = get_name(dref); if (dn != 0 && dn != "") { if (strstr(dn, "purecall") != -1) return 1; if (strstr(dn, "pure_virtual") != -1) return 1; } dref = get_next_dref_from(faddr, dref); } auto tgt = get_first_fcref_from(faddr); if (tgt != BADADDR) { if (is_known_purecall_addr(tgt)) return 1; auto tn = get_func_name(tgt); if (tn != 0 && tn != "") { if (strstr(tn, "purecall") != -1) return 1; if (strstr(tn, "pure_virtual") != -1) return 1; } } } return 0; } // ========================================================================= // DESTRUTOR // ========================================================================= static is_destructor(faddr) { if (faddr == BADADDR || faddr == 0) return 0; auto name = get_func_name(faddr); if (name != 0 && name != "") { if (strstr(name, "::~") != -1) return 1; if (strstr(name, "??1") != -1) return 1; if (strstr(name, "??_E") != -1) return 1; if (strstr(name, "??_G") != -1) return 1; if (strstr(name, "destructor") != -1) return 1; if (strstr(name, "scalar_dtor")!= -1) return 1; if (strstr(name, "vector_dtor")!= -1) return 1; if (strstr(name, "__dtor") != -1) return 1; } auto fend = get_func_attr(faddr, FUNCATTR_END); if (fend == BADADDR) return 0; if ((fend - faddr) > 0x200) return 0; auto ea = faddr; auto guard = 0; while (ea < fend && ea != BADADDR && guard < 80) { auto m = print_insn_mnem(ea); if (m == "call" || m == "jmp") { auto tgt = get_first_fcref_from(ea); if (tgt != BADADDR) { auto tn = get_func_name(tgt); if (tn != 0 && tn != "") { if (strstr(tn, "operator delete") != -1) return 1; if (strstr(tn, "??3@") != -1) return 1; if (strstr(tn, "??_V") != -1) return 1; if (tn == "_free" || tn == "free" || tn == "_free_base") return 1; } } } ea = next_head(ea, fend); guard = guard + 1; } return 0; } // ========================================================================= // DETECCAO DE SIZEOF VIA operator new // Pattern em x86 thiscall: // push <imm> ; size // call ?? operator new // add esp, 4 ; (opcional) // mov ecx, eax ; this = obj // call <ctor> // ========================================================================= static find_object_size_from_call(call_ea) { auto fstart = get_func_attr(call_ea, FUNCATTR_START); if (fstart == BADADDR) fstart = call_ea - 0x80; auto cur = call_ea; auto guard = 0; while (guard < 15) { cur = prev_head(cur, fstart); if (cur == BADADDR || cur == 0 || cur < fstart) break; auto m = print_insn_mnem(cur); if (m == "ret" || m == "jmp") break; if (m == "call") { auto tgt = get_first_fcref_from(cur); if (tgt != BADADDR) { auto tn = get_func_name(tgt); if (tn != 0 && tn != "") { auto is_alloc = 0; if (strstr(tn, "operator new") != -1) is_alloc = 1; if (strstr(tn, "??2@") != -1) is_alloc = 1; if (strstr(tn, "_malloc") != -1) is_alloc = 1; if (tn == "malloc" || tn == "_malloc") is_alloc = 1; if (is_alloc) { auto pcur = cur; auto pg = 0; while (pg < 6) { pcur = prev_head(pcur, fstart); if (pcur == BADADDR || pcur < fstart) break; auto pm = print_insn_mnem(pcur); if (!g_is_64 && pm == "push" && get_operand_type(pcur, 0) == o_imm) { return get_operand_value(pcur, 0); } if (g_is_64 && pm == "mov" && get_operand_type(pcur, 0) == o_reg && get_operand_type(pcur, 1) == o_imm) { auto v = get_operand_value(pcur, 1); if (v > 0 && v < 0x10000) return v; } pg = pg + 1; } } } } return 0; } guard = guard + 1; } return 0; } static find_object_size_for_ctor(ctor_ea) { auto xr = get_first_cref_to(ctor_ea); while (xr != BADADDR) { auto sz = find_object_size_from_call(xr); if (sz > 0 && sz < 0x10000) return sz; xr = get_next_cref_to(ctor_ea, xr); } return 0; } // ========================================================================= // DETECCAO DE SINGLETON // Heuristica: depois da call ao construtor, se ha "mov ds:G, eax" com G // sendo um global valido, marca G como instancia singleton da classe. // ========================================================================= // Marca um global como singleton da classe. static mark_singleton(g_ea, class_name, ctor_ea) { if (DRY_RUN) return; auto cur_name = get_name(g_ea); if (cur_name == "" || starts_with(cur_name, "dword_") || starts_with(cur_name, "qword_") || starts_with(cur_name, "byte_") || starts_with(cur_name, "off_") || starts_with(cur_name, "unk_")) { set_name(g_ea, "g_" + class_name + "_instance", SN_NOWARN | SN_FORCE); } set_cmt(g_ea, "Singleton " + class_name + " ctor=0x" + ltoa(ctor_ea, 16), 1); } static detect_singleton_for_ctor(ctor_ea, class_name) { auto xr = get_first_cref_to(ctor_ea); while (xr != BADADDR) { auto caller_end = get_func_attr(xr, FUNCATTR_END); if (caller_end == BADADDR) caller_end = xr + 0x100; auto tracked = "|eax|rax|ecx|rcx|"; auto guard = 0; auto cur = xr; while (guard < 8) { cur = next_head(cur, caller_end); if (cur == BADADDR || cur >= caller_end) break; auto m = print_insn_mnem(cur); if (m == "call" || m == "ret" || m == "jmp") break; if (m == "mov") { auto t0 = get_operand_type(cur, 0); auto t1 = get_operand_type(cur, 1); if (t0 == o_mem && t1 == o_reg) { auto src_needle = "|" + print_operand(cur, 1) + "|"; if (strstr(tracked, src_needle) != -1) { auto g_ea = get_operand_value(cur, 0); if (is_loaded(g_ea) && g_ea > 0x1000) { mark_singleton(g_ea, class_name, ctor_ea); return g_ea; } } } if (t0 == o_reg && t1 == o_reg) { auto src_needle2 = "|" + print_operand(cur, 1) + "|"; if (strstr(tracked, src_needle2) != -1) { auto dst_needle = "|" + print_operand(cur, 0) + "|"; if (strstr(tracked, dst_needle) == -1) { tracked = tracked + substr(dst_needle, 1, strlen(dst_needle)); } } } } guard = guard + 1; } xr = get_next_cref_to(ctor_ea, xr); } return BADADDR; } // ========================================================================= // APLICACAO DE __thiscall + tipo "this" NAS FUNCOES VIRTUAIS // ========================================================================= // FIX A1: nao caimos mais em SetType (pode nao existir em IDA 9.x). // FIX A2: usamos TINFO_GUESSED (0) para nao sobrescrever tipos que o // usuario tenha definido manualmente. static apply_type_safe(ea, decl) { auto ti = parse_decl(decl, 0); if (ti == 0) return 0; return apply_type(ea, ti, 0); } static ensure_class_struct(sname_class) { auto sname = sname_class + "_t"; auto sid = get_struc_id(sname); if (sid != BADADDR) return sid; sid = add_struc(-1, sname, 0); if (sid == BADADDR) return BADADDR; auto flag = g_is_64 ? (FF_QWORD | FF_DATA) : (FF_DWORD | FF_DATA); add_struc_member(sid, "vptr", 0, flag, -1, g_ptr_size); return sid; } static apply_thiscall_to_vtable(vt_ea) { if (!APPLY_THISCALL_TYPES || DRY_RUN) return 0; auto sname = hier_name(vt_ea); auto count = hier_count(vt_ea); if (sname == "" || count == 0) return 0; auto effective_sname = sname; auto col_offset = hier_offset(vt_ea); if (col_offset != 0) { auto col = hier_col(vt_ea); if (col != BADADDR) { auto ptd = find_parent_td_for_offset(col, col_offset); if (ptd != 0) { auto pvt = hier_vt_by_td(ptd); if (pvt != BADADDR) { auto pn = hier_name(pvt); if (pn != "") effective_sname = pn; } } } } auto sid = ensure_class_struct(effective_sname); if (sid == BADADDR) return 0; auto applied = 0; auto i; for (i = 0; i < count; i = i + 1) { auto slot = vt_ea + i * g_ptr_size; auto faddr = read_ptr(slot); if (faddr == 0 || faddr == BADADDR) continue; if (is_pure_call(faddr)) continue; auto decl = "void __thiscall sub(struct " + effective_sname + "_t *this);"; if (apply_type_safe(faddr, decl)) applied = applied + 1; } return applied; } static apply_all_thiscall_types() { if (!APPLY_THISCALL_TYPES) return; log_info("=== Aplicando __thiscall + tipo this (em ordem topologica) ==="); auto tcount = topo_count(); auto total = (tcount > 0) ? tcount : g_vt_list_count; auto i; for (i = 0; i < total; i = i + 1) { if (user_cancelled()) return; auto vt = (tcount > 0) ? topo_at(i) : hier_vt_at(i); if (vt != BADADDR) { auto n = apply_thiscall_to_vtable(vt); g_stat_thiscall = g_stat_thiscall + n; } if ((i & 0x1F) == 0) { msg("[thiscall] %d/%d classes | %d metodos\n", i, total, g_stat_thiscall); } } log_info("__thiscall aplicado em " + ltoa(g_stat_thiscall, 10) + " metodos"); } // ========================================================================= // ANOTACAO DE CHAMADAS VIRTUAIS (call dword ptr [reg+N]) // ========================================================================= // // Plausibilidade v2: rastreia o registrador-base do call ate sua origem. // // 1) extrai o reg-base de "call [reg+N]" (ex: "eax" em "[eax+0Ch]") // 2) caminha pra tras ate o inicio da funcao (ou ate ret/jmp/call), // acompanhando o conjunto de regs que carregam aquele valor (lida // com propagacoes "mov ebx, eax" ao longo do prologo) // 3) declara plausivel quando encontra o write final que veio de // memoria (mov reg, [reg2+disp] ou lea reg, [reg2+disp]) - o // vptr load // 4) se o write veio de imediato/aritmetica/etc, descarta // // Janela aumentada pra 32 instrucoes (ou ate o inicio da funcao). // ========================================================================= static get_call_displ_base_reg(ea) { auto op = print_operand(ea, 0); if (op == 0 || op == "") return ""; auto bp = strstr(op, "["); if (bp == -1) return ""; auto i = bp + 1; auto n = strlen(op); auto reg = ""; while (i < n) { auto c = substr(op, i, i + 1); if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || (c >= "0" && c <= "9")) { reg = reg + c; } else { break; } i = i + 1; } return reg; } static is_likely_vcall(call_ea) { auto call_reg = get_call_displ_base_reg(call_ea); if (call_reg == "") return 0; auto fstart = get_func_attr(call_ea, FUNCATTR_START); if (fstart == BADADDR) fstart = call_ea - 0x100; auto tracked = "|" + call_reg + "|"; auto cur = call_ea; auto guard = 0; while (guard < 32) { cur = prev_head(cur, fstart); if (cur == BADADDR || cur < fstart) break; auto m = print_insn_mnem(cur); if (m == "ret" || m == "jmp" || m == "call") break; if (m == "mov" || m == "lea") { auto t0 = get_operand_type(cur, 0); if (t0 == o_reg) { auto dst = print_operand(cur, 0); auto dst_needle = "|" + dst + "|"; if (strstr(tracked, dst_needle) != -1) { auto t1 = get_operand_type(cur, 1); if (t1 == o_phrase || t1 == o_displ) return 1; if (t1 == o_reg) { auto src = print_operand(cur, 1); auto src_needle = "|" + src + "|"; if (strstr(tracked, src_needle) == -1) { tracked = tracked + substr(src_needle, 1, strlen(src_needle)); } } else { return 0; } } } } guard = guard + 1; } return 0; } static build_slot_methods_map() { arr_init(ARR_SLOT_METHODS); auto i; for (i = 0; i < g_vt_list_count; i = i + 1) { auto vt = hier_vt_at(i); if (vt == BADADDR) continue; auto sname = hier_name(vt); auto count = hier_count(vt); auto j; for (j = 0; j < count; j = j + 1) { auto cur = arr_get_str(ARR_SLOT_METHODS, j); if (cur == "") { arr_set_str(ARR_SLOT_METHODS, j, sname); } else { if (strlen(cur) < 400) { arr_set_str(ARR_SLOT_METHODS, j, cur + "," + sname); } } } } } static get_slot_summary(slot) { auto cur = arr_get_str(ARR_SLOT_METHODS, slot); if (cur == "") return ""; auto out = ""; auto buf = ""; auto emitted = 0; auto total = 0; auto i; auto n = strlen(cur); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(cur, i, i + 1) : ","; if (c == ",") { if (buf != "") { total = total + 1; if (emitted < VCALL_MAX_METHODS) { if (out != "") out = out + ", "; out = out + buf + "::v" + ltoa(slot, 10); emitted = emitted + 1; } } buf = ""; } else { buf = buf + c; } } if (total > VCALL_MAX_METHODS) { out = out + " (+" + ltoa(total - VCALL_MAX_METHODS, 10) + " mais)"; } return out; } static annotate_virtual_calls() { if (!ANNOTATE_VCALLS || DRY_RUN) return; log_info("=== Anotando chamadas virtuais ==="); build_slot_methods_map(); auto annotated = 0; auto func_count = 0; auto f = get_next_func(0); while (f != BADADDR) { if (user_cancelled()) return; func_count = func_count + 1; auto end = get_func_attr(f, FUNCATTR_END); auto ea = f; while (ea < end && ea != BADADDR) { auto m = print_insn_mnem(ea); if (m == "call" && get_operand_type(ea, 0) == o_displ) { auto disp = get_operand_value(ea, 0); if (disp >= 0 && disp < 0x800 && (disp - ((disp / g_ptr_size) * g_ptr_size)) == 0) { auto slot = disp / g_ptr_size; auto summary = get_slot_summary(slot); if (summary != "" && is_likely_vcall(ea)) { auto cur_cmt = get_cmt(ea, 0); auto can_set = 0; if (cur_cmt == "" || cur_cmt == 0) can_set = 1; else if (starts_with(cur_cmt, "vcall ")) can_set = 1; if (can_set) { set_cmt(ea, "vcall slot[" + ltoa(slot, 10) + "]: " + summary, 0); annotated = annotated + 1; } } } } ea = next_head(ea, end); } f = get_next_func(f); if ((func_count & 0xFF) == 0) { msg("[vcall] %d funcoes | %d call sites anotados\n", func_count, annotated); } } g_stat_vcalls = annotated; log_info("vcall: " + ltoa(annotated, 10) + " call sites anotados"); } // ========================================================================= // GERACAO DE wyd_classes.h (topological) // ========================================================================= static emit_class_header_rec(hf, vt_ea) { if (arr_get_long(ARR_HDR_EMITTED, vt_ea) > 0) return; arr_set_long(ARR_HDR_EMITTED, vt_ea, 1); if (hier_offset(vt_ea) != 0) return; auto parents_csv = hier_parents(vt_ea); auto parent_names_buf = ""; auto parent_count = 0; auto buf = ""; auto i; auto n = strlen(parents_csv); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(parents_csv, i, i + 1) : ","; if (c == ",") { if (buf != "") { auto td = atol(buf); auto pvt = hier_vt_by_td(td); if (pvt != BADADDR) { emit_class_header_rec(hf, pvt); auto pname = hier_name(pvt); if (parent_names_buf == "") parent_names_buf = pname; else parent_names_buf = parent_names_buf + "|" + pname; parent_count = parent_count + 1; } } buf = ""; } else { buf = buf + c; } } auto cname = hier_name(vt_ea); auto count = hier_count(vt_ea); auto size = arr_get_long(ARR_VT2SIZE, vt_ea); auto sgl = arr_get_long(ARR_VT2SINGLETON, vt_ea); fprintf(hf, "// vtable=0x%x methods=%d", vt_ea, count); if (size > 0) fprintf(hf, " sizeof=0x%x", size); if (sgl != -1 && sgl != BADADDR) fprintf(hf, " singleton=0x%x", sgl); if (parent_count > 0) { fprintf(hf, " inherits="); auto pn = parent_names_buf; auto j; auto pbuf = ""; auto first_p = 1; auto pl = strlen(pn); for (j = 0; j <= pl; j = j + 1) { auto pc = (j < pl) ? substr(pn, j, j + 1) : "|"; if (pc == "|") { if (pbuf != "") { if (!first_p) fprintf(hf, ","); fprintf(hf, "%s", pbuf); first_p = 0; } pbuf = ""; } else { pbuf = pbuf + pc; } } } fprintf(hf, "\n"); fprintf(hf, "struct vtable_%s_t;\n", cname); fprintf(hf, "struct %s {\n", cname); fprintf(hf, " struct vtable_%s_t *vptr;\n", cname); if (size > g_ptr_size) { fprintf(hf, " char data[0x%x];\n", size - g_ptr_size); } fprintf(hf, " // %d virtual methods:\n", count); auto k; for (k = 0; k < count; k = k + 1) { auto slot = vt_ea + k * g_ptr_size; auto faddr = read_ptr(slot); auto fname = get_func_name(faddr); auto pos = strstr(fname, "__"); auto mp; if (pos != -1) mp = substr(fname, pos + 2, strlen(fname)); else if (fname != "" && fname != 0) mp = fname; else mp = "vmethod_" + ltoa(k, 10); fprintf(hf, " // [%d] @ 0x%x %s", k, faddr, mp); if (is_pure_call(faddr)) fprintf(hf, " (pure)"); fprintf(hf, "\n"); } fprintf(hf, "};\n\n"); g_stat_header_classes = g_stat_header_classes + 1; } static emit_cpp_header(out_path) { if (!EMIT_CPP_HEADER) return; log_info("=== Gerando header C++ ==="); auto hf = fopen(out_path, "w"); if (hf == 0) { log_warn("Nao consegui abrir " + out_path); return; } fprintf(hf, "// =====================================================\n"); fprintf(hf, "// Gerado por Vtable Analyzer Pro v5.1\n"); fprintf(hf, "// Total de classes: %d\n", g_vt_list_count); fprintf(hf, "// Use no IDA: File > Load file > Parse C header file...\n"); fprintf(hf, "// para importar como Local Types.\n"); fprintf(hf, "// =====================================================\n"); fprintf(hf, "\n#pragma pack(push, 1)\n\n"); arr_init(ARR_HDR_EMITTED); auto i; for (i = 0; i < g_vt_list_count; i = i + 1) { if (user_cancelled()) break; auto vt = hier_vt_at(i); if (vt != BADADDR) emit_class_header_rec(hf, vt); } fprintf(hf, "#pragma pack(pop)\n"); fclose(hf); log_info("Header gerado: " + out_path + " (" + ltoa(g_stat_header_classes, 10) + " classes)"); } // ========================================================================= // CONSTRUTOR / DESTRUTOR via xref // ========================================================================= static rename_constructors(vtable_ea, sname_class) { auto xr = get_first_dref_to(vtable_ea); auto ctor_idx = 0; auto dtor_idx = 0; auto first_ctor = BADADDR; while (xr != BADADDR) { auto fstart = get_func_attr(xr, FUNCATTR_START); auto xr_mnem = print_insn_mnem(xr); auto vtable_assign = (xr_mnem == "mov" || xr_mnem == "lea"); if (fstart != BADADDR && vtable_assign) { auto fend = get_func_attr(fstart, FUNCATTR_END); auto fsize = fend - fstart; auto rel = xr - fstart; auto early = (fsize > 0) && (rel < (fsize / 2 + 8) || rel < 64); if (early) { auto cur_name = get_func_name(fstart); auto auto_named = (cur_name == "" || starts_with(cur_name, "sub_")); auto is_dtor = is_destructor(fstart); if (is_dtor) { g_stat_dtors = g_stat_dtors + 1; if ((auto_named || RENAME_OVERRIDES) && !DRY_RUN) { auto dname; if (dtor_idx == 0) dname = sname_class + "__dtor"; else dname = sname_class + "__dtor_" + ltoa(dtor_idx, 10); set_name(fstart, dname, SN_NOWARN | SN_FORCE); set_color(fstart, CIC_FUNC, COLOR_DTOR); log_info(" [DTOR-renamed] " + dname + " @ 0x" + ltoa(fstart, 16)); } else { if (!DRY_RUN) set_color(fstart, CIC_FUNC, COLOR_DTOR); log_info(" [DTOR-detected] " + cur_name + " @ 0x" + ltoa(fstart, 16)); } dtor_idx = dtor_idx + 1; } else { g_stat_ctors = g_stat_ctors + 1; if ((auto_named || RENAME_OVERRIDES) && !DRY_RUN) { auto cname2; if (ctor_idx == 0) cname2 = sname_class + "__ctor"; else cname2 = sname_class + "__ctor_" + ltoa(ctor_idx, 10); set_name(fstart, cname2, SN_NOWARN | SN_FORCE); set_color(fstart, CIC_FUNC, COLOR_CTOR); log_info(" [CTOR-renamed] " + cname2 + " @ 0x" + ltoa(fstart, 16)); } else { if (!DRY_RUN) set_color(fstart, CIC_FUNC, COLOR_CTOR); log_info(" [CTOR-detected] " + cur_name + " @ 0x" + ltoa(fstart, 16)); } ctor_idx = ctor_idx + 1; if (first_ctor == BADADDR) first_ctor = fstart; } } } xr = get_next_dref_to(vtable_ea, xr); } if (first_ctor != BADADDR) { if (DETECT_OBJECT_SIZES) { auto size = find_object_size_for_ctor_v2(first_ctor); if (size > 0 && size < 0x10000) { arr_set_long(ARR_VT2SIZE, vtable_ea, size); if (!DRY_RUN) { set_cmt(vtable_ea, sprintf("vtable de %s sizeof=0x%X (%d bytes)", sname_class, size, size), 1); } g_stat_sizes = g_stat_sizes + 1; log_info(" [SIZE] sizeof(" + sname_class + ") = 0x" + ltoa(size, 16)); } } if (DETECT_SINGLETONS) { auto sgl = detect_singleton_for_ctor(first_ctor, sname_class); if (sgl == BADADDR) { sgl = detect_singleton_aggressive(first_ctor, sname_class); } if (sgl != BADADDR) { arr_set_long(ARR_VT2SINGLETON, vtable_ea, sgl); g_stat_singletons = g_stat_singletons + 1; log_info(" [SGTN] " + sname_class + " instance @ 0x" + ltoa(sgl, 16)); } } } } // ========================================================================= // TAMANHO DA VTABLE // ========================================================================= static is_another_vtable_starting_here(slot_ea) { auto xr = get_first_dref_to(slot_ea); while (xr != BADADDR) { auto sn = seg_name_of(xr); if (strstr(sn, ".rdata") != -1 || strstr(sn, ".data") != -1) return 1; xr = get_next_dref_to(slot_ea, xr); } return 0; } static vtable_size(vtable_ea) { auto count = 0; auto cur = vtable_ea; while (count < MAX_VTABLE_METHODS) { auto p = read_ptr(cur); if (!is_valid_code_ptr(p)) break; if (count > 0 && is_another_vtable_starting_here(cur)) break; count = count + 1; cur = cur + g_ptr_size; } return count; } // ========================================================================= // STRUCT DA VTABLE // ========================================================================= static create_vtable_struct(sname_class, vtable_ea, count) { if (!CREATE_STRUCTS) return; if (DRY_RUN) return; auto sname = "vtable_" + sname_class + "_t"; auto sid = get_struc_id(sname); if (sid != BADADDR) del_struc(sid); sid = add_struc(-1, sname, 0); if (sid == BADADDR) { log_warn(" Falha ao criar struct " + sname); return; } auto i; for (i = 0; i < count; i = i + 1) { auto slot = vtable_ea + (i * g_ptr_size); auto faddr = read_ptr(slot); auto fname = get_func_name(faddr); auto member; if (fname == 0 || fname == "" || starts_with(fname, "sub_")) { member = "vmethod_" + ltoa(i, 10); } else { auto pos = strstr(fname, "::"); if (pos != -1) member = substr(fname, pos + 2, strlen(fname)); else member = fname; member = sanitize_name(member); } auto base_member = member; auto suffix = 0; while (get_member_offset(sid, member) != -1 && suffix < 64) { suffix = suffix + 1; member = base_member + "_" + ltoa(suffix, 10); } auto flag = g_is_64 ? (FF_QWORD | FF_DATA) : (FF_DWORD | FF_DATA); add_struc_member(sid, member, i * g_ptr_size, flag, -1, g_ptr_size); } log_info(" [STRUCT] " + sname + " (" + ltoa(count, 10) + " slots)"); } // ========================================================================= // EMISSAO DE JSON (uma classe) // ========================================================================= // // FIX item 2: emit_parents_json itera direto sobre o CSV numerico de TDs, // sem nunca passar pela string "pretty" (que tinha ", " e quebrava com // templates demangled). // static emit_parents_json(jf, csv) { if (jf == 0 || csv == "" || csv == 0) return; auto buf = ""; auto first = 1; auto i; auto n = strlen(csv); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(csv, i, i + 1) : ","; if (c == ",") { if (buf != "") { auto td = atol(buf); auto mang = read_typedesc_name(td); auto nm = (mang != "") ? demangle_class_name(mang) : "??"; if (!first) fprintf(jf, ", "); fprintf(jf, "\"%s\"", json_escape(nm)); first = 0; } buf = ""; } else { buf = buf + c; } } } static emit_class_json(cname, sname, mangled, vt_ea, col_ea, count, parents_csv) { auto jf = g_json_handle; if (jf == 0) return; if (!g_json_first) fprintf(jf, ",\n"); g_json_first = 0; fprintf(jf, " {\n"); fprintf(jf, " \"class\": \"%s\",\n", json_escape(cname)); fprintf(jf, " \"sanitized\": \"%s\",\n", json_escape(sname)); fprintf(jf, " \"mangled\": \"%s\",\n", json_escape(mangled)); fprintf(jf, " \"vtable_ea\": \"0x%x\",\n", vt_ea); fprintf(jf, " \"col_ea\": \"0x%x\",\n", col_ea); fprintf(jf, " \"method_count\": %d,\n", count); fprintf(jf, " \"parents\": ["); emit_parents_json(jf, parents_csv); fprintf(jf, "],\n"); auto col_offset_v = arr_get_long(ARR_VT2OFFSET, vt_ea); if (col_offset_v == -1) col_offset_v = 0; if (col_offset_v != 0) { fprintf(jf, " \"is_secondary\": true,\n"); fprintf(jf, " \"sub_offset\": %d,\n", col_offset_v); } auto size_v = arr_get_long(ARR_VT2SIZE, vt_ea); auto sgl_v = arr_get_long(ARR_VT2SINGLETON, vt_ea); if (size_v > 0) fprintf(jf, " \"sizeof\": %d,\n", size_v); if (sgl_v != -1 && sgl_v != BADADDR) fprintf(jf, " \"singleton_ea\": \"0x%x\",\n", sgl_v); fprintf(jf, " \"methods\": [\n"); auto j; for (j = 0; j < count; j = j + 1) { auto slot = vt_ea + (j * g_ptr_size); auto faddr = read_ptr(slot); auto kind = "vmethod"; if (is_pure_call(faddr)) kind = "pure"; else if (j <= 1 && is_destructor(faddr)) kind = "deleting_dtor"; fprintf(jf, " {\"index\": %d, \"slot_ea\": \"0x%x\", \"func_ea\": \"0x%x\", \"name\": \"%s\", \"kind\": \"%s\"}", j, slot, faddr, json_escape(get_func_name(faddr)), kind); if (j < count - 1) fprintf(jf, ","); fprintf(jf, "\n"); } fprintf(jf, " ]\n"); fprintf(jf, " }"); } // ========================================================================= // v5.3: FILTRO DE FRAMEWORK CLASSES // Evita poluir o output com classes do MFC/STL/ATL que o usuario raramente // quer mexer. Identificacao por prefixo de nome. // ========================================================================= static is_framework_class(name) { if (name == 0 || name == "") return 0; if (starts_with(name, "std::")) return 1; if (starts_with(name, "ATL::")) return 1; if (starts_with(name, "Gdiplus::")) return 1; if (starts_with(name, "Json::")) return 1; if (starts_with(name, "CMFC")) return 1; if (starts_with(name, "CMap<")) return 1; if (starts_with(name, "CArray<")) return 1; if (starts_with(name, "CList<")) return 1; if (starts_with(name, "CTypedPtr")) return 1; if (starts_with(name, "CResourcePool<")) return 1; if (starts_with(name, "DataPool<")) return 1; if (starts_with(name, "COle")) return 1; if (starts_with(name, "CDoc")) return 1; if (starts_with(name, "CDock")) return 1; if (starts_with(name, "CWnd")) return 1; if (starts_with(name, "CWin")) return 1; if (starts_with(name, "CCmd")) return 1; if (starts_with(name, "_AFX")) return 1; if (starts_with(name, "AFX_")) return 1; if (strstr(name, "tpl_") != -1) return 1; if (name == "type_info") return 1; return 0; } // ========================================================================= // v5.3: SIZEOF DETECTOR v2 // Tenta multiplos padroes pra detectar sizeof do objeto: // 1. push imm + call new (padrao MSVC stdlib) // 2. mov ecx/edx, imm + call <custom_alloc> // 3. mov [esp], imm + call <alloc> // 4. (fallback) inferir pelo max offset escrito em [this+N] no body do ctor // ========================================================================= static find_object_size_v2_caller(call_ea) { auto fstart = get_func_attr(call_ea, FUNCATTR_START); if (fstart == BADADDR) fstart = call_ea - 0x80; auto cur = call_ea; auto guard = 0; while (guard < 20) { cur = prev_head(cur, fstart); if (cur == BADADDR || cur == 0 || cur < fstart) break; auto m = print_insn_mnem(cur); if (m == "ret" || m == "jmp") break; if (m == "call") { auto tgt = get_first_fcref_from(cur); if (tgt != BADADDR) { auto tn = get_func_name(tgt); if (tn != 0 && tn != "") { auto is_alloc = 0; if (strstr(tn, "operator new") != -1) is_alloc = 1; if (strstr(tn, "??2@") != -1) is_alloc = 1; if (strstr(tn, "_malloc") != -1) is_alloc = 1; if (tn == "malloc" || tn == "_malloc") is_alloc = 1; if (strstr(tn, "Alloc") != -1) is_alloc = 1; if (strstr(tn, "alloc") != -1) is_alloc = 1; if (strstr(tn, "MemNew") != -1) is_alloc = 1; if (is_alloc) { auto pcur = cur; auto pg = 0; while (pg < 8) { pcur = prev_head(pcur, fstart); if (pcur == BADADDR || pcur < fstart) break; auto pm = print_insn_mnem(pcur); if (pm == "push" && get_operand_type(pcur, 0) == o_imm) { return get_operand_value(pcur, 0); } if (pm == "mov" && get_operand_type(pcur, 0) == o_reg && get_operand_type(pcur, 1) == o_imm) { auto v = get_operand_value(pcur, 1); if (v > 0 && v < 0x10000) return v; } if (pm == "mov" && get_operand_type(pcur, 0) == o_displ && get_operand_type(pcur, 1) == o_imm) { auto v2 = get_operand_value(pcur, 1); if (v2 > 0 && v2 < 0x10000) return v2; } pg = pg + 1; } } } } return 0; } guard = guard + 1; } return 0; } static infer_size_from_ctor_body(ctor_ea) { if (!SIZE_INFER_FROM_BODY) return 0; auto fend = get_func_attr(ctor_ea, FUNCATTR_END); if (fend == BADADDR) return 0; if ((fend - ctor_ea) > 0x800) return 0; auto max_off = 0; auto ea = ctor_ea; auto guard = 0; while (ea < fend && ea != BADADDR && guard < SIZE_BODY_SCAN_LIMIT) { auto i; for (i = 0; i < 2; i = i + 1) { if (get_operand_type(ea, i) == o_displ) { auto disp = get_operand_value(ea, i); if (disp > max_off && disp < 0x10000) { auto op_str = print_operand(ea, i); if (strstr(op_str, "ecx") != -1 || strstr(op_str, "esi") != -1 || strstr(op_str, "edi") != -1 || strstr(op_str, "ebx") != -1) { max_off = disp; } } } } ea = next_head(ea, fend); guard = guard + 1; } if (max_off == 0) return 0; return max_off + g_ptr_size; } static find_object_size_for_ctor_v2(ctor_ea) { auto xr = get_first_cref_to(ctor_ea); while (xr != BADADDR) { auto sz = find_object_size_v2_caller(xr); if (sz > 0 && sz < 0x10000) return sz; xr = get_next_cref_to(ctor_ea, xr); } auto body_sz = infer_size_from_ctor_body(ctor_ea); if (body_sz > 0) return body_sz; return 0; } // ========================================================================= // v5.3: SINGLETON DETECTOR v2 (mais agressivo) // Alem de "mov ds:G, eax" pos-ctor, detecta: // - Funcao curta que retorna sempre o mesmo global ("GetInstance" pattern) // - Lazy init pattern: "if (!g_X) g_X = new X(); return g_X;" // ========================================================================= static detect_singleton_aggressive(ctor_ea, class_name) { if (!SINGLETON_AGGRESSIVE) return BADADDR; auto xr = get_first_cref_to(ctor_ea); while (xr != BADADDR) { auto caller_start = get_func_attr(xr, FUNCATTR_START); if (caller_start == BADADDR) { xr = get_next_cref_to(ctor_ea, xr); continue; } auto caller_end = get_func_attr(caller_start, FUNCATTR_END); auto caller_size = caller_end - caller_start; if (caller_size > 0 && caller_size < 0x100) { auto cur = caller_start; auto g = 0; while (cur < caller_end && cur != BADADDR && g < 30) { auto m = print_insn_mnem(cur); if (m == "mov" && get_operand_type(cur, 0) == o_reg && get_operand_type(cur, 1) == o_mem) { auto g_ea = get_operand_value(cur, 1); if (is_loaded(g_ea) && g_ea > 0x1000) { auto cn = get_func_name(caller_start); if (cn != "" && (strstr(cn, "etInstance") != -1 || strstr(cn, "etSingleton") != -1 || strstr(cn, "_instance") != -1 || caller_size < 0x40)) { mark_singleton(g_ea, class_name, ctor_ea); return g_ea; } } } cur = next_head(cur, caller_end); g = g + 1; } } xr = get_next_cref_to(ctor_ea, xr); } return BADADDR; } // ========================================================================= // ANALISE DE UMA VTABLE // ========================================================================= static analyze_vtable(ea) { if (cache_seen(ea)) return 0; auto first_ptr = read_ptr(ea); if (!is_valid_code_ptr(first_ptr)) return 0; auto col_ea = read_ptr(ea - g_ptr_size); if (!validate_col(col_ea)) return 0; auto type_desc = col_read_field(col_ea, 0x0C); auto mangled = read_typedesc_name(type_desc); if (mangled == "") return 0; auto cname = demangle_class_name(mangled); if (cname == "") return 0; if (EXCLUDE_FRAMEWORK_CLASSES && is_framework_class(cname)) { g_stat_filtered = g_stat_filtered + 1; cache_mark(ea); return 0; } auto col_offset = get_wide_dword(col_ea + 0x04); auto sname = sanitize_name(cname); auto display_name = cname; if (col_offset != 0) { sname = sname + "_sub_" + ltoa(col_offset, 16); display_name = cname + " [secondary @ +0x" + ltoa(col_offset, 16) + "]"; } auto count = vtable_size(ea); if (count < MIN_VTABLE_METHODS) return 0; auto parents_csv = parse_base_classes(col_ea); auto parents_pretty; if (col_offset != 0) { auto ptd = find_parent_td_for_offset(col_ea, col_offset); if (ptd != 0) { auto pmang = read_typedesc_name(ptd); parents_pretty = (pmang != "") ? demangle_class_name(pmang) : "??"; } else { parents_pretty = ""; } } else { parents_pretty = parents_csv_to_names(parents_csv); } log_info("Classe: " + display_name + " (" + ltoa(count, 10) + " metodos) vtable=0x" + ltoa(ea, 16) + " COL=0x" + ltoa(col_ea, 16)); log_info(" mangled = " + mangled); if (parents_pretty != "") { if (col_offset != 0) log_info(" base = " + parents_pretty); else log_info(" pais = " + parents_pretty); } g_stat_classes = g_stat_classes + 1; g_stat_methods = g_stat_methods + count; if (!DRY_RUN) { set_name(ea, "vtable_" + sname, SN_NOWARN | SN_FORCE); set_name(col_ea, "RTTI_COL_" + sname, SN_NOWARN | SN_FORCE); set_color(col_ea, CIC_ITEM, COLOR_RTTI_COL); set_cmt(col_ea, "CompleteObjectLocator de " + cname + (col_offset != 0 ? sprintf(" (offset 0x%X)", col_offset) : ""), 1); if (col_offset == 0) { set_name(type_desc, "RTTI_TD_" + sname, SN_NOWARN | SN_FORCE); set_color(type_desc, CIC_ITEM, COLOR_TYPE_DESC); set_cmt(type_desc, "TypeDescriptor de " + cname, 1); } if (col_offset != 0) { set_cmt(ea, "vtable secundaria de " + cname + " base " + (parents_pretty != "" ? parents_pretty : "??") + " @ offset 0x" + ltoa(col_offset, 16), 1); } else if (parents_pretty != "") { set_cmt(ea, "vtable de " + cname + " herda de: " + parents_pretty, 1); } else { set_cmt(ea, "vtable de " + cname, 1); } } auto i; for (i = 0; i < count; i = i + 1) { auto slot = ea + (i * g_ptr_size); auto faddr = read_ptr(slot); auto vname; auto vcolor = COLOR_VFUNC; if (is_pure_call(faddr)) { vname = sname + "__pure_v" + ltoa(i, 10); vcolor = COLOR_PURE; g_stat_pures = g_stat_pures + 1; } else if (i <= 1 && is_destructor(faddr)) { vname = sname + "__deleting_dtor_v" + ltoa(i, 10); vcolor = COLOR_DTOR; } else { vname = sname + "__vmethod_" + ltoa(i, 10); } auto cur_name = get_func_name(faddr); auto can_rename = (cur_name == "" || starts_with(cur_name, "sub_") || RENAME_OVERRIDES); if (!DRY_RUN && can_rename) { if (set_name(faddr, vname, SN_NOWARN | SN_FORCE)) { g_stat_renamed = g_stat_renamed + 1; } } if (!DRY_RUN) { set_color(slot, CIC_ITEM, COLOR_VTABLE_SLOT); set_color(faddr, CIC_FUNC, vcolor); set_cmt(slot, sprintf("[%d] +0x%X -> %s", i, i * g_ptr_size, vname), 0); } } create_vtable_struct(sname, ea, count); if (col_offset == 0) rename_constructors(ea, sname); hier_register(type_desc, ea, col_ea, sname, count, parents_csv, col_offset); emit_class_json(display_name, sname, mangled, ea, col_ea, count, parents_csv); cache_mark(ea); return count; } // ========================================================================= // STRING-FIRST: helpers // ========================================================================= // v5.2 DEBUG: loga em detalhe cada xref do TD e o resultado da validacao // pra entender por que estamos perdendo as vtables. static validate_col_verbose(col_ea) { if (!is_valid_data_ptr(col_ea)) { msg(" [reject] data_ptr invalido\n"); return 0; } auto sig = get_wide_dword(col_ea); auto expected = g_is_64 ? 1 : 0; if (sig != expected) { msg(" [reject] sig=0x%x esperado=%d\n", sig, expected); return 0; } auto offset = get_wide_dword(col_ea + 0x04); auto cdOffset = get_wide_dword(col_ea + 0x08); if (offset > 0x10000) { msg(" [reject] offset=0x%x grande demais\n", offset); return 0; } if (cdOffset > 0x10000) { msg(" [reject] cdOffset=0x%x grande demais\n", cdOffset); return 0; } auto type_desc = col_read_field(col_ea, 0x0C); if (!is_valid_data_ptr(type_desc)) { msg(" [reject] type_desc=0x%x invalido\n", type_desc); return 0; } auto chd = col_read_field(col_ea, 0x10); if (!is_valid_data_ptr(chd)) { msg(" [reject] chd=0x%x invalido\n", chd); return 0; } auto chd_sig = get_wide_dword(chd); if (chd_sig != 0) { msg(" [reject] chd_sig=0x%x (esperado 0)\n", chd_sig); return 0; } auto nb = get_wide_dword(chd + 0x08); if (nb == 0 || nb > 256) { msg(" [reject] num_bases=%d\n", nb); return 0; } if (g_is_64) { auto self_rva = get_wide_dword(col_ea + 0x14); if (rva_to_ea(self_rva) != col_ea) { msg(" [reject] pSelf nao bate\n"); return 0; } } msg(" [accept] COL valido!\n"); return 1; } static process_typedesc_xrefs(td_ea) { auto found = 0; auto xr = get_first_dref_to(td_ea); while (xr != BADADDR) { auto col_candidate = xr - 0x0C; if (validate_col(col_candidate)) { auto xv = get_first_dref_to(col_candidate); while (xv != BADADDR) { auto vtable_candidate = xv + g_ptr_size; if (is_valid_code_ptr(read_ptr(vtable_candidate))) { if (analyze_vtable(vtable_candidate) > 0) found = found + 1; } xv = get_next_dref_to(col_candidate, xv); } } xr = get_next_dref_to(td_ea, xr); } return found; } static byte_at(ea) { return get_wide_byte(ea); } static scan_via_rtti_strings() { log_info("=== Modo string-first ==="); auto total = 0; auto strings_found = 0; auto iters = 0; auto seg; for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg)) { auto sn = get_segm_name(seg); if (strstr(sn, ".rdata") == -1 && strstr(sn, ".data") == -1) continue; auto ss = get_segm_start(seg); auto se = get_segm_end(seg); if (STRING_SCAN_MAX_SEG_SIZE > 0 && (se - ss) > STRING_SCAN_MAX_SEG_SIZE) { log_warn("Pulando " + sn + " (tamanho " + ltoa(se - ss, 16) + " > limite " + ltoa(STRING_SCAN_MAX_SEG_SIZE, 16) + ")"); continue; } log_info("Procurando .?AV/.?AU em " + sn + " [0x" + ltoa(ss, 16) + " - 0x" + ltoa(se, 16) + "]"); auto ea = ss; while (ea < se - 8) { if (user_cancelled()) return total; iters = iters + 1; if ((iters % (PROGRESS_EVERY * 4)) == 0) { msg("[string-scan] 0x%x | strings: %d | classes: %d\n", ea, strings_found, g_stat_classes); } if (byte_at(ea) == 0x2E && byte_at(ea + 1) == 0x3F && byte_at(ea + 2) == 0x41 && (byte_at(ea + 3) == 0x56 || byte_at(ea + 3) == 0x55)) { auto mangled = read_c_string_raw(ea); if (mangled != 0 && mangled != "" && ends_with(mangled, "@@")) { strings_found = strings_found + 1; auto td_ea = ea - (g_ptr_size * 2); if (is_valid_data_ptr(td_ea)) { total = total + process_typedesc_xrefs(td_ea); } auto sl = strlen(mangled); ea = ea + sl + 1; } else { ea = ea + 1; } } else { ea = ea + 1; } } } log_info("string-first: " + ltoa(strings_found, 10) + " strings RTTI vistas, " + ltoa(total, 10) + " novas vtables analisadas"); return total; } // ========================================================================= // PONTEIRO-SEQUENCIAL // ========================================================================= static scan_via_pointers() { log_info("=== Modo ponteiro-sequencial ==="); auto total = 0; auto iters = 0; auto seg; for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg)) { auto sn = get_segm_name(seg); auto match = (strstr(sn, ".rdata") != -1); if (SCAN_DATA_TOO && strstr(sn, ".data") != -1 && strstr(sn, ".rdata") == -1) match = 1; if (!match) continue; auto ss = get_segm_start(seg); auto se = get_segm_end(seg); log_info("Varrendo " + sn + " [0x" + ltoa(ss, 16) + " - 0x" + ltoa(se, 16) + "]"); auto ea = ss; while (ea < se) { if (user_cancelled()) return total; iters = iters + 1; if ((iters % PROGRESS_EVERY) == 0) { msg("[ponteiro-scan] 0x%x | classes: %d | metodos: %d\n", ea, g_stat_classes, g_stat_methods); } auto consumed = analyze_vtable(ea); if (consumed > 0) { total = total + 1; ea = ea + (consumed * g_ptr_size); } else { ea = ea + g_ptr_size; } } } log_info("ponteiro-scan: " + ltoa(total, 10) + " vtables analisadas"); return total; } // ========================================================================= // BRUTE-FORCE RTTI (x64 e x86) - fallback quando IDA nao tracou xrefs // // Estrategia identica em ambas arquiteturas, apenas a assinatura do COL // e o tamanho do ponteiro mudam: // // 1) varremos .rdata em alinhamento 4 procurando COL candidatos: // x64: signature == 1 e pSelf RVA consistente // x86: signature == 0 e validate_col passa // 2) para cada COL valido, tentamos xrefs; se nao houver, fazemos // um segundo scan procurando ptrs (qword em x64, dword em x86) // que apontam pro COL - isso eh vtable[-1]. // // ========================================================================= static align_up_4(ea) { auto rem = ea - ((ea / 4) * 4); if (rem == 0) return ea; return ea + (4 - rem); } static align_up_8(ea) { auto rem = ea - ((ea / 8) * 8); if (rem == 0) return ea; return ea + (8 - rem); } static brute_force_vt_for_col(col_ea) { auto found = 0; auto seg; for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg)) { auto sn = get_segm_name(seg); if (strstr(sn, ".rdata") == -1) continue; auto ss = get_segm_start(seg); auto se = get_segm_end(seg); auto ea = g_is_64 ? align_up_8(ss) : align_up_4(ss); while (ea < se - g_ptr_size * 2) { if (user_cancelled()) return found; auto cur_val = read_ptr(ea); if (cur_val == col_ea) { auto vt = ea + g_ptr_size; if (is_valid_code_ptr(read_ptr(vt))) { if (analyze_vtable(vt) > 0) found = found + 1; } } ea = ea + g_ptr_size; } } return found; } static brute_force_rtti() { if (g_is_64) { if (!BRUTE_FORCE_X64_RVA) return 0; } else { if (!BRUTE_FORCE_X86) return 0; } auto expected_sig = g_is_64 ? 1 : 0; auto arch_label = g_is_64 ? "x64" : "x86"; log_info("=== Brute-force " + arch_label + " (COL signature=" + ltoa(expected_sig, 10) + ") ==="); auto found = 0; auto iters = 0; auto seg; for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg)) { if (user_cancelled()) return found; auto sn = get_segm_name(seg); if (strstr(sn, ".rdata") == -1) continue; auto ss = get_segm_start(seg); auto se = get_segm_end(seg); log_info("Brute-scan em " + sn + " [0x" + ltoa(ss, 16) + " - 0x" + ltoa(se, 16) + "]"); auto ea = align_up_4(ss); while (ea < se - 24) { if (user_cancelled()) return found; iters = iters + 1; if ((iters % PROGRESS_EVERY) == 0) { msg("[brute-scan] 0x%x | achadas: %d\n", ea, g_stat_brute); } if (get_wide_dword(ea) == expected_sig) { auto cheap_ok = 1; if (g_is_64) { auto self_rva = get_wide_dword(ea + 0x14); if (rva_to_ea(self_rva) != ea) cheap_ok = 0; } if (cheap_ok && validate_col(ea)) { auto vt_found = 0; auto xv = get_first_dref_to(ea); while (xv != BADADDR) { auto vt = xv + g_ptr_size; if (is_valid_code_ptr(read_ptr(vt))) { if (analyze_vtable(vt) > 0) { vt_found = vt_found + 1; found = found + 1; g_stat_brute = g_stat_brute + 1; } } xv = get_next_dref_to(ea, xv); } if (vt_found == 0) { auto extra = brute_force_vt_for_col(ea); found = found + extra; g_stat_brute = g_stat_brute + extra; } } } ea = ea + 4; if ((ea & 0xFFFF) == 0) { } } } log_info("brute " + arch_label + ": " + ltoa(found, 10) + " novas vtables"); return found; } static brute_force_x64() { return brute_force_rtti(); } // ========================================================================= // PROPAGACAO DE OVERRIDES // // Para cada classe com pais, percorre o primeiro pai (ancestral imediato) // e compara slot a slot: // - Mesmo func ptr -> METODO HERDADO (apenas registra) // - Func ptr difere -> OVERRIDE (comenta no slot e na funcao) // // Tambem emite vtables_overrides.json com as relacoes encontradas. // ========================================================================= static emit_override_json(child_name, child_vt, slot_idx, parent_name, parent_func, child_func) { auto of = g_overrides_handle; if (of == 0) return; if (!g_overrides_first) fprintf(of, ",\n"); g_overrides_first = 0; fprintf(of, " {\"child\": \"%s\", \"slot\": %d, \"parent\": \"%s\", \"parent_func\": \"0x%x\", \"child_func\": \"0x%x\"}", child_name, slot_idx, parent_name, parent_func, child_func); } static first_parent_td(parents_csv) { if (parents_csv == "" || parents_csv == 0) return 0; auto pos = strstr(parents_csv, ","); auto first = (pos != -1) ? substr(parents_csv, 0, pos) : parents_csv; return atol(first); } static find_parent_td_for_offset(col_ea, target_offset) { if (target_offset == 0) return 0; auto chd = col_read_field(col_ea, 0x10); if (!is_valid_data_ptr(chd)) return 0; auto num_bases = get_wide_dword(chd + 0x08); if (num_bases <= 1 || num_bases > 64) return 0; auto bca_field = get_wide_dword(chd + 0x0C); auto bca_ea = g_is_64 ? rva_to_ea(bca_field) : bca_field; if (!is_valid_data_ptr(bca_ea)) return 0; auto i; for (i = 1; i < num_bases; i = i + 1) { auto bcd_field = get_wide_dword(bca_ea + i * 4); auto bcd_ea = g_is_64 ? rva_to_ea(bcd_field) : bcd_field; if (!is_valid_data_ptr(bcd_ea)) continue; auto mdisp = get_wide_dword(bcd_ea + 0x08); if (mdisp == target_offset) { auto td_field = get_wide_dword(bcd_ea); return g_is_64 ? rva_to_ea(td_field) : td_field; } } return 0; } // ========================================================================= // TOPOLOGICAL SORT (pais antes de filhos) // ========================================================================= static topo_dfs(vt_ea, count) { if (arr_get_long(ARR_TOPO_VISITED, vt_ea) > 0) return count; arr_set_long(ARR_TOPO_VISITED, vt_ea, 1); auto parents = hier_parents(vt_ea); auto buf = ""; auto i; auto n = strlen(parents); for (i = 0; i <= n; i = i + 1) { auto c = (i < n) ? substr(parents, i, i + 1) : ","; if (c == ",") { if (buf != "") { auto td = atol(buf); auto pvt = hier_vt_by_td(td); if (pvt != BADADDR) count = topo_dfs(pvt, count); } buf = ""; } else { buf = buf + c; } } arr_set_long(ARR_TOPO_ORDER, count, vt_ea); return count + 1; } static topo_build() { arr_init(ARR_TOPO_VISITED); arr_init(ARR_TOPO_ORDER); arr_init(ARR_TOPO_COUNT); auto count = 0; auto i; for (i = 0; i < g_vt_list_count; i = i + 1) { if (user_cancelled()) break; auto vt = hier_vt_at(i); if (vt != BADADDR) count = topo_dfs(vt, count); } arr_set_long(ARR_TOPO_COUNT, 0, count); log_info("Topological order: " + ltoa(count, 10) + " classes"); return count; } static topo_at(idx) { auto v = arr_get_long(ARR_TOPO_ORDER, idx); if (v == -1) return BADADDR; return v; } static topo_count() { auto v = arr_get_long(ARR_TOPO_COUNT, 0); if (v == -1) return 0; return v; } static propagate_overrides() { if (!PROPAGATE_OVERRIDES) return; if (g_vt_list_count == 0) return; auto tcount = topo_count(); if (tcount == 0) return; log_info("=== Passada de overrides em ordem topologica (" + ltoa(tcount, 10) + " classes) ==="); auto i; for (i = 0; i < tcount; i = i + 1) { if (user_cancelled()) return; auto vt_ea = topo_at(i); if (vt_ea == BADADDR) continue; auto parents_csv = hier_parents(vt_ea); if (parents_csv == "") continue; auto col_offset = hier_offset(vt_ea); auto parent_td; if (col_offset == 0) { parent_td = first_parent_td(parents_csv); } else { auto col_ea2 = hier_col(vt_ea); if (col_ea2 == BADADDR) continue; parent_td = find_parent_td_for_offset(col_ea2, col_offset); } if (parent_td == 0) continue; auto parent_vt = hier_vt_by_td(parent_td); if (parent_vt == BADADDR) { continue; } auto child_name = hier_name(vt_ea); auto parent_name = hier_name(parent_vt); auto own_count = hier_count(vt_ea); auto parent_count = hier_count(parent_vt); auto limit = (parent_count < own_count) ? parent_count : own_count; auto j; for (j = 0; j < limit; j = j + 1) { auto cslot = vt_ea + j * g_ptr_size; auto pslot = parent_vt + j * g_ptr_size; auto cfunc = read_ptr(cslot); auto pfunc = read_ptr(pslot); if (cfunc == pfunc) { g_stat_inherited = g_stat_inherited + 1; if (!DRY_RUN) { auto pname = get_func_name(pfunc); auto child_pattern = child_name + "__vmethod_" + ltoa(j, 10); if (pname == child_pattern) { auto canonical = parent_name + "__vmethod_" + ltoa(j, 10); if (set_name(pfunc, canonical, SN_NOWARN | SN_FORCE)) { g_stat_renamed_inherited = g_stat_renamed_inherited + 1; pname = canonical; } } set_cmt(cslot, sprintf("[%d] HERDADO de %s (-> %s)", j, parent_name, pname), 0); } } else { g_stat_overrides = g_stat_overrides + 1; if (!DRY_RUN) { auto pname2 = get_func_name(pfunc); set_cmt(cslot, sprintf("[%d] OVERRIDE de %s::%s", j, parent_name, pname2), 0); set_color(cslot, CIC_ITEM, COLOR_OVERRIDE); auto cfn = get_func_name(cfunc); auto rep = "Override de " + parent_name + "::" + pname2; auto existing = get_func_cmt(cfunc, 1); auto can_set_func_cmt = 0; if (existing == "" || existing == 0) can_set_func_cmt = 1; else if (starts_with(existing, "Override de ")) can_set_func_cmt = 1; if (can_set_func_cmt) set_func_cmt(cfunc, rep, 1); } emit_override_json(child_name, vt_ea, j, parent_name, pfunc, cfunc); } } } log_info("Overrides: " + ltoa(g_stat_overrides, 10) + " Herdados: " + ltoa(g_stat_inherited, 10)); } // ========================================================================= // MAIN // ========================================================================= static main() { if (CLEAR_OUTPUT_ON_START) { process_ui_action("msglist:Clear", 0); } msg("\n========================================\n"); msg("Vtable Analyzer Pro v5.3 - iniciando...\n"); msg("========================================\n"); if (REGISTER_HOTKEY) { add_idc_hotkey(HOTKEY_COMBO, "main"); msg("[INFO] Hotkey %s registrada para re-rodar o script\n", HOTKEY_COMBO); } g_is_64 = (get_inf_attr(INF_LFLAGS) & LFLG_64BIT) ? 1 : 0; g_ptr_size = g_is_64 ? 8 : 4; g_imagebase = get_imagebase(); g_json_first = 1; g_overrides_first = 1; g_vt_list_count = 0; g_stat_classes = 0; g_stat_methods = 0; g_stat_renamed = 0; g_stat_ctors = 0; g_stat_dtors = 0; g_stat_pures = 0; g_stat_overrides = 0; g_stat_inherited = 0; g_stat_brute = 0; g_stat_thiscall = 0; g_stat_sizes = 0; g_stat_singletons = 0; g_stat_vcalls = 0; g_stat_header_classes = 0; g_stat_renamed_inherited = 0; g_purecall_csv = ""; g_stat_filtered = 0; g_stat_errors = 0; auto out_dir = resolve_output_dir(); auto json_path = path_join(out_dir, OUTPUT_JSON_NAME); auto log_path = path_join(out_dir, OUTPUT_LOG_NAME); auto overrides_path = path_join(out_dir, OUTPUT_OVERRIDES_NAME); auto header_path = path_join(out_dir, OUTPUT_HEADER_NAME); msg("[INFO] Pasta de saida: %s\n", out_dir); msg("[INFO] JSON esperado em: %s\n", json_path); msg("[INFO] LOG esperado em: %s\n", log_path); msg("[INFO] OVERRIDES esperado em: %s\n", overrides_path); msg("[INFO] HEADER esperado em: %s\n", header_path); g_log_handle = safe_fopen(log_path, "w"); if (g_log_handle == 0) { msg("[WARN] Log em arquivo desabilitado, continuando so com console.\n"); } log_info("===================================================="); log_info(" Vtable Analyzer Pro v5.3 - IDA Free 9.x"); log_info("===================================================="); log_info("Arquitetura : " + (g_is_64 ? "x64" : "x86")); log_info("Ptr size : " + ltoa(g_ptr_size, 10)); log_info("Image base : 0x" + ltoa(g_imagebase, 16)); log_info("Pasta saida : " + out_dir); log_info("Dry run : " + (DRY_RUN ? "SIM" : "NAO")); log_info("Cria struct : " + (CREATE_STRUCTS ? "SIM" : "NAO")); log_info("Sobrescreve : " + (RENAME_OVERRIDES ? "SIM" : "NAO")); log_info("Ponteiro-scan: " + (POINTER_FIRST_SCAN ? "SIM" : "NAO")); log_info("String-scan : " + (STRING_FIRST_SCAN ? "SIM" : "NAO")); log_info("Overrides : " + (PROPAGATE_OVERRIDES ? "SIM" : "NAO")); log_info("Brute x64 : " + (BRUTE_FORCE_X64_RVA ? "SIM" : "NAO") + (g_is_64 ? "" : " (inerte em x86)")); log_info("Brute x86 : " + (BRUTE_FORCE_X86 ? "SIM" : "NAO") + (g_is_64 ? " (inerte em x64)" : "")); log_info("__thiscall : " + (APPLY_THISCALL_TYPES ? "SIM" : "NAO")); log_info("Sizeof obj : " + (DETECT_OBJECT_SIZES ? "SIM" : "NAO")); log_info("Singletons : " + (DETECT_SINGLETONS ? "SIM" : "NAO")); log_info("VCall annotat: " + (ANNOTATE_VCALLS ? "SIM" : "NAO")); log_info("CPP header : " + (EMIT_CPP_HEADER ? "SIM" : "NAO")); log_info("Excl framewrk: " + (EXCLUDE_FRAMEWORK_CLASSES ? "SIM" : "NAO")); log_info("Size inferenc: " + (SIZE_INFER_FROM_BODY ? "SIM" : "NAO")); log_info("Singl agressv: " + (SINGLETON_AGGRESSIVE ? "SIM" : "NAO")); cache_init(); init_purecall_addresses(); if (g_purecall_csv != "") { log_info("purecall enderecos conhecidos: " + g_purecall_csv); } g_json_handle = safe_fopen(json_path, "w"); if (g_json_handle != 0) { fprintf(g_json_handle, "{\n"); fprintf(g_json_handle, " \"meta\": {\n"); fprintf(g_json_handle, " \"arch\": \"%s\",\n", g_is_64 ? "x64" : "x86"); fprintf(g_json_handle, " \"ptr_size\": %d,\n", g_ptr_size); fprintf(g_json_handle, " \"imagebase\": \"0x%x\",\n", g_imagebase); fprintf(g_json_handle, " \"tool\": \"vtable_analyzer_pro_v5.2\",\n"); fprintf(g_json_handle, " \"stats_note\": \"methods=total slots; pures/inherited/overrides sao subconjuntos disjuntos de methods\"\n"); fprintf(g_json_handle, " },\n"); fprintf(g_json_handle, " \"classes\": [\n"); } g_overrides_handle = safe_fopen(overrides_path, "w"); if (g_overrides_handle != 0) { fprintf(g_overrides_handle, "{\n \"overrides\": [\n"); } if (POINTER_FIRST_SCAN) scan_via_pointers(); if (STRING_FIRST_SCAN) scan_via_rtti_strings(); if ((g_is_64 && BRUTE_FORCE_X64_RVA) || (!g_is_64 && BRUTE_FORCE_X86)) brute_force_rtti(); topo_build(); propagate_overrides(); apply_all_thiscall_types(); annotate_virtual_calls(); emit_cpp_header(header_path); if (g_json_handle != 0) { fprintf(g_json_handle, "\n ],\n"); fprintf(g_json_handle, " \"stats\": {\n"); fprintf(g_json_handle, " \"classes\": %d,\n", g_stat_classes); fprintf(g_json_handle, " \"methods\": %d,\n", g_stat_methods); fprintf(g_json_handle, " \"renamed\": %d,\n", g_stat_renamed); fprintf(g_json_handle, " \"ctors\": %d,\n", g_stat_ctors); fprintf(g_json_handle, " \"dtors\": %d,\n", g_stat_dtors); fprintf(g_json_handle, " \"pures\": %d,\n", g_stat_pures); fprintf(g_json_handle, " \"overrides\": %d,\n", g_stat_overrides); fprintf(g_json_handle, " \"inherited\": %d,\n", g_stat_inherited); fprintf(g_json_handle, " \"brute_x64\": %d,\n", g_stat_brute); fprintf(g_json_handle, " \"thiscall\": %d,\n", g_stat_thiscall); fprintf(g_json_handle, " \"sizes\": %d,\n", g_stat_sizes); fprintf(g_json_handle, " \"singletons\":%d,\n", g_stat_singletons); fprintf(g_json_handle, " \"vcalls\": %d,\n", g_stat_vcalls); fprintf(g_json_handle, " \"hdr_class\": %d,\n", g_stat_header_classes); fprintf(g_json_handle, " \"renamed_inherited\": %d,\n", g_stat_renamed_inherited); fprintf(g_json_handle, " \"methods_concrete\": %d,\n", g_stat_methods - g_stat_pures); fprintf(g_json_handle, " \"filtered\": %d,\n", g_stat_filtered); fprintf(g_json_handle, " \"errors\": %d\n", g_stat_errors); fprintf(g_json_handle, " }\n}\n"); fclose(g_json_handle); } if (g_overrides_handle != 0) { fprintf(g_overrides_handle, "\n ]\n}\n"); fclose(g_overrides_handle); } log_info("===================================================="); log_info(" RESUMO"); log_info(" Classes encontradas : " + ltoa(g_stat_classes, 10)); log_info(" Metodos virtuais : " + ltoa(g_stat_methods, 10)); log_info(" Funcoes renomeadas : " + ltoa(g_stat_renamed, 10)); log_info(" Construtores : " + ltoa(g_stat_ctors, 10)); log_info(" Destrutores : " + ltoa(g_stat_dtors, 10)); log_info(" Purecalls : " + ltoa(g_stat_pures, 10)); log_info(" Overrides marcados : " + ltoa(g_stat_overrides, 10)); log_info(" Metodos herdados : " + ltoa(g_stat_inherited, 10)); log_info(" Brute x64 : " + ltoa(g_stat_brute, 10)); log_info(" __thiscall : " + ltoa(g_stat_thiscall, 10)); log_info(" Sizes detectados : " + ltoa(g_stat_sizes, 10)); log_info(" Singletons : " + ltoa(g_stat_singletons, 10)); log_info(" VCall sites : " + ltoa(g_stat_vcalls, 10)); log_info(" Classes no header : " + ltoa(g_stat_header_classes, 10)); log_info(" Renorm. herdados : " + ltoa(g_stat_renamed_inherited, 10)); log_info(" Metodos concretos : " + ltoa(g_stat_methods - g_stat_pures, 10)); log_info(" Filtradas (framework): " + ltoa(g_stat_filtered, 10)); log_info(" Erros : " + ltoa(g_stat_errors, 10)); log_info("===================================================="); log_info("JSON : " + json_path); log_info("OVERRIDES : " + overrides_path); log_info("HEADER : " + header_path); log_info("LOG : " + log_path); log_info(""); log_info("=== ARQUIVOS GERADOS ==="); auto check_h; check_h = fopen(json_path, "r"); if (check_h != 0) { fseek(check_h, 0, 2); log_info(sprintf(" JSON : %d bytes", ftell(check_h))); fclose(check_h); } else { log_info(" JSON : NAO GERADO"); } check_h = fopen(overrides_path, "r"); if (check_h != 0) { fseek(check_h, 0, 2); log_info(sprintf(" OVERRIDES : %d bytes", ftell(check_h))); fclose(check_h); } else { log_info(" OVERRIDES : NAO GERADO"); } check_h = fopen(header_path, "r"); if (check_h != 0) { fseek(check_h, 0, 2); log_info(sprintf(" HEADER : %d bytes", ftell(check_h))); fclose(check_h); } else { log_info(" HEADER : NAO GERADO"); } if (g_log_handle != 0) fclose(g_log_handle); msg("\n=== Vtable Analyzer Pro v5.3 finalizado ===\n"); msg("Classes=%d Metodos=%d Renomeadas=%d Ctors=%d Dtors=%d Pures=%d Overrides=%d\n", g_stat_classes, g_stat_methods, g_stat_renamed, g_stat_ctors, g_stat_dtors, g_stat_pures, g_stat_overrides); msg("ThisCall=%d Sizes=%d Singletons=%d VCalls=%d HdrClasses=%d\n", g_stat_thiscall, g_stat_sizes, g_stat_singletons, g_stat_vcalls, g_stat_header_classes); }
- 5 respostas
-
- 78
-
-
-
-
- 1 resposta
-
- 26
-
-
-
Segue uma ideia de como esta ficando a nova Interface
-
Implementação da nova interface e melhora do código geral.
- 42 respostas
-
- 15
-
-
-
Caraca só os cabelo branco... hehe
-
Implementado o suporte para o sistema de paineis do WYD Global.
- 42 respostas
-
- 20
-
-
-
[Em Desenvolvimento] Bot de discord para tutoriais de WYD Projeto inicial
gcr_fc respondeu ao tópico de -GooD- em WYD - Desenvolvimento
Bacana, integrei um sistema de monitoramento de crash no cliente DX11 que envia o dump pro Discord automaticamente quando um crash ocorre, facilita muito a análise. Ws possibilidades de integração com o Discord tornam as coisas bem interessantes hehe- 2 respostas
-
- 11
-
-
-
ah pode cre, o claude é brabo, já usei o opus, simplesmente não da pra competir... vc só precisa ser mais esperto que a IA, pq o conhecimento ela tem...
-
qual ia vc esta usando? esta usando a interface copilot integrada no VS da pra usar a do chat gpt tbm. Tem que aproveitar, ajuda muito no trabalho, ainda mais se faz sozinho... sem contar que facilita muito a organização do projeto, o que precisa ser feito, o que ja foi feito e por ai vai.
-
Cara é pura e simplesmente analise do binário do global... O Sniffer que disponibilizei, ajuda muito, fiz uma mudança nele e faço comparação automática das STRUCTS dos packets da TMProject e das que conhecemos com as atuais do Global, foi assim que descobri muita coisa, dai pra frente vc cria as structs no IDA e vai analisando função por função... Se tivesse uma equipe seria muito mais rápido, essa BASE_GetCurrentScore ai, todos os calculos são exatamente o que é usado hj no Global. Tbm implementei um sistema que posso editar ingame qualquer coisa do cliente, por exemplo quero saber o que um byte que desconheço na STRUCT_MOB modifica, simplesmente digito o comando mob.set e o que quero modificar e vejo visualmente no cliente o que mudou, então consigo saber exatamente o que cada campo da struct faz no jogo... por hora é só client side pq a ideia não é criar hack ou coisa assim, só conhecer realmente as structs do cliente, mas como conheço as keys usadas para tratar packets falsos, conseguimos facilmente criar packets validos e enviar para o servidor. aqui modifiquei a str da currentscore so de exemplo. é trabalhoso, mas funciona bem... Obviamente os comandos digitados só aparecem para mim...
- 8 respostas
-
- 23
-
-
void BASE_GetCurrentScore(STRUCT_MOB_GLOBAL* MOB, STRUCT_AFFECT* Affect, STRUCT_EXT1* Ext1, STRUCT_EXT2* Ext2, int OriginalFace, int TargetX, int TargetY, int* AnotherSkill, int soultype, int* BuffState) //Função Ok { MOB->Rsv = 0; int MOBMagic = 0; memcpy(&MOB->CurrentScore, &MOB->BaseScore, sizeof STRUCT_SCORE); int tDamage = MOB->CurrentScore.Damage; int tMaxHp = MOB->CurrentScore.MaxHp; if (BASE_IsTranscended(OriginalFace) == 1) { //(0: Não iniciou a quest - 1: usou o cristal Elime - 2: usou o cristal Silphed - 3: usou o cristal Thelion - 4: usou o cristal Noas) for (int i = 0; i < 4; i++) { if (!BASE_TestByteBit(Ext2->Quest[1], i)) continue; int BonusMP = ResultQuest355[i][0]; int BonusAC = ResultQuest355[i][1]; int BonusHP = ResultQuest355[i][2]; MOB->CurrentScore.MaxMp += BonusMP; MOB->CurrentScore.Ac += BonusAC; tMaxHp += BonusHP; } } int acAdd = BASE_GetMobAbilityGlobal(MOB, EF_AC) + BASE_GetMobAbilityGlobal(MOB, EF_ACADD); tDamage += BASE_GetMobAbilityGlobal(MOB, EF_DAMAGE); tMaxHp += BASE_GetMobAbilityGlobal(MOB, EF_HP); MOB->CurrentScore.Ac += acAdd; MOB->CurrentScore.MaxMp += BASE_GetMobAbilityGlobal(MOB, EF_MP); MOB->CurrentScore.Str += BASE_GetMobAbilityGlobal(MOB, EF_STR); MOB->CurrentScore.Int += BASE_GetMobAbilityGlobal(MOB, EF_INT); MOB->CurrentScore.Dex += BASE_GetMobAbilityGlobal(MOB, EF_DEX); MOB->CurrentScore.Con += BASE_GetMobAbilityGlobal(MOB, EF_CON); int special0 = MOB->CurrentScore.Special[0] + BASE_GetMobAbilityGlobal(MOB, EF_SPECIAL1); if (special0 > MAX_SPECIAL_G) special0 = MAX_SPECIAL_G; int special1 = MOB->CurrentScore.Special[1] + BASE_GetMobAbilityGlobal(MOB, EF_SPECIAL2) + BASE_GetMobAbilityGlobal(MOB, EF_SPECIALALL); if (special1 > MAX_SPECIAL_G) special1 = MAX_SPECIAL_G; int special2 = MOB->CurrentScore.Special[2] + BASE_GetMobAbilityGlobal(MOB, EF_SPECIAL3) + BASE_GetMobAbilityGlobal(MOB, EF_SPECIALALL); if (special2 > MAX_SPECIAL_G) special2 = MAX_SPECIAL_G; int special3 = MOB->CurrentScore.Special[3] + BASE_GetMobAbilityGlobal(MOB, EF_SPECIAL4) + BASE_GetMobAbilityGlobal(MOB, EF_SPECIALALL); if (special3 > MAX_SPECIAL_G) special3 = MAX_SPECIAL_G; MOB->CurrentScore.Special[0] = special0; MOB->CurrentScore.Special[1] = special1; MOB->CurrentScore.Special[2] = special2; MOB->CurrentScore.Special[3] = special3; MOB->SaveMana = BASE_GetMobAbilityGlobal(MOB, EF_SAVEMANA); MOBMagic = BASE_GetMobAbilityGlobal(MOB, EF_MAGIC) + BASE_GetMobAbilityGlobal(MOB, EF_MAGICADD); int movSpeed = MOB->CurrentScore.AttackRun & 0xF; movSpeed += BASE_GetMobAbilityGlobal(MOB, EF_RUNSPEED); int attSpeed = BASE_GetMobAbilityGlobal(MOB, EF_ATTSPEED); int regenHP = BASE_GetMobAbilityGlobal(MOB, EF_REGENHP); int regenMP = BASE_GetMobAbilityGlobal(MOB, EF_REGENMP); int faceindex = MOB->Equip[FACE].sIndex; if (faceindex <= 0 || (faceindex >= 22 && faceindex <= 25) || faceindex == 32) //Transformações do BM faceindex = OriginalFace; int face = faceindex / 10; if (face < 4) { MOB->Equip[FACE].stEffect[0].cEffect = EF_SANC; MOB->Equip[FACE].stEffect[0].cValue = 0; if (MOB->Clan == 7 || MOB->Clan == 8 || MOB->Clan == 9) MOB->Clan = 0; int mountId = MOB->Equip[MOUNT].sIndex; int isNipple = FALSE; if ((TargetX / 128) >= 27 && (TargetX / 128) <= 30 && (TargetY / 128) >= 21 && (TargetY / 128) <= 24) //X 3456 - 3840 Y 2688 - 3072 isNipple = TRUE; if (isNipple) movSpeed -= 2; if (MOB->Equip[MOUNT].stEffect[0].sValue > 0) //Mount Hp { int nCategory = (mountId >= 1 && mountId < MAX_ITEMLIST) ? g_pItemList[mountId].nCategory : 0; if (nCategory == 22 || nCategory == 23) { int nSubCategory = g_pItemList[mountId].nSubCategory; if (nSubCategory >= 0 && nSubCategory < MOUNT_BONUS_ENTRIES) movSpeed = g_pMountBonusTable[nSubCategory].nMountSpeed; } } if (movSpeed < 0) movSpeed = 0; if (movSpeed > 6) movSpeed = 6; } if (BASE_IsTranscended(OriginalFace) == 1 && (MOB->LearnedSkill[0] & LEARN_30) != 0) //verifica se o personagem é Celestial+ checando a face e se tem soul { if (MOB->Class == 0) { tDamage += MOB->CurrentScore.Damage + 880; tMaxHp += 1600; MOB->CurrentScore.Ac += 950; MOB->CurrentScore.MaxMp += 300; } else if (MOB->Class == 1) { tDamage += 800; tMaxHp += 800; MOB->CurrentScore.Ac += 950; MOB->CurrentScore.MaxMp += 1700; } else if (MOB->Class == 2) { tDamage += 880; tMaxHp += 800; MOB->CurrentScore.Ac += 950; MOB->CurrentScore.MaxMp += 1300; } else if (MOB->Class == 3) { tDamage += 800; tMaxHp += 800; MOB->CurrentScore.Ac += 950; MOB->CurrentScore.MaxMp += 700; } } int capeId = MOB->Equip[CAPE].sIndex; //ebp-11c if (MOB->Clan != 4 && (capeId == 543 || capeId == 545 || capeId == 1766 || capeId == 1767 || capeId == 1768 || capeId == 3191 || capeId == 3194 || capeId == 3197 || capeId == 6447)) { MOB->Clan = 7; } if (MOB->Clan != 4 && (capeId == 544 || capeId == 546 || capeId == 1769 || capeId == 1770 || capeId == 1771 || capeId == 3192 || capeId == 3195 || capeId == 3198 || capeId == 6448)) { MOB->Clan = 8; } if (MOB->Clan != 4 && capeId == 1720) { MOB->Clan = 9; } if (MOB->Clan != 4 && (capeId == 734 || capeId == 736)) { MOB->Clan = 7; } if (MOB->Clan != 4 && (capeId == 735 || capeId == 737)) { MOB->Clan = 8; } if (MOB->Class == 3 && (MOB->LearnedSkill[0] & LEARN_02) != 0) //Agressividade { int weaponId = MOB->Equip[WEAPON].sIndex; if (weaponId > 0 && weaponId < MAX_ITEMLIST) { int itemUnique = g_pItemList[weaponId].nUnique; if (itemUnique == 42 || itemUnique == 43) //Arcos ou Garras { tDamage = tDamage * (special1 / 20 + 100) / 100; } } } int hpAdd = BASE_GetMobAbilityGlobal(MOB, EF_HPADD) + 100; tMaxHp = (tMaxHp * hpAdd) / 100; int mpAdd = BASE_GetMobAbilityGlobal(MOB, EF_MPADD) + 100; MOB->CurrentScore.MaxMp = (MOB->CurrentScore.MaxMp * mpAdd) / 100; for (int i = HELMET; i <= SHIELD; i++) { int equipId = MOB->Equip[i].sIndex; if (equipId <= 0 || equipId >= MAX_ITEMLIST) continue; int itemSanc = BASE_GetItemSanc(&MOB->Equip[i]); if (itemSanc < 9) continue; int itemPos = g_pItemList[equipId].nPos; if (itemPos == ARMOR_POS) MOB->CurrentScore.Ac += 25; else if (itemPos == PANTS_POS) MOB->CurrentScore.Ac += 25; else if (itemPos == GLOVES_POS) MOB->Rsv |= 64; else if (itemPos == SHIELD_POS) MOB->CurrentScore.Ac += 25; else if (itemPos == WEAPON_POS || itemPos == WEAPON2HAND_POS) { int itemUnique = g_pItemList[equipId].nUnique; if (itemUnique == 47) MOBMagic += 16; else if (itemUnique == 44) MOBMagic += 16; else tDamage += 40; } } int AttackSpeedBonus = 0; int RunSpeedBonus = 0; int DAMAGEMULTI = 100; int HPMULTI = 100; int MPMULTI = 100; int StatePlusHp = FALSE; int tMaxHPb = 0; *BuffState = 0; int Resist[4]{}; Resist[0] = MOB->Resist[0]; Resist[1] = MOB->Resist[1]; Resist[2] = MOB->Resist[2]; Resist[3] = MOB->Resist[3]; for (int i = 0; i < MAX_AFFECT; i++) { if (!Affect) continue; int Type = Affect[i].Type; if (!Type) continue; int Value = Affect[i].Value; int Level = Affect[i].Level; if (Type == 1) //Ice: Holy_Touch, Freeze_Spiral, Ice_Spear, Rock_Of_Blizzard, Thunder_Lising, Ice_Armor, Ambush { int scaledLevel = 4 * Level; if (scaledLevel >= 255) movSpeed -= 3; else movSpeed -= 1; if (Value == 1) *BuffState |= FROZEN; else if ((Value == 3 || Value == 50) && movSpeed > 1) movSpeed = 1; if (scaledLevel < 10) scaledLevel = 10; if (scaledLevel > 999) scaledLevel = 999; MOB->MultiHitThreshold = MOB->MultiHitThreshold * (1000 - scaledLevel) / 1000; } else if (Type == 2) //Velocity: Haste (Aceleração) { movSpeed += Value; *BuffState |= VELOCITY; } else if (Type == 3) //Pursuit: Double_Swing (Golpe Duplo), Resi_Decrease { int scaledLevel = 4 * Level; int decResist = Value; if (MOB->Equip[FACE].sIndex < 50) { if ((MOB->LearnedSkill[0] & LEARN_23) != 0) decResist = Value - 40; else decResist = Value - 30; } if (scaledLevel > 255) { int pct = scaledLevel / 50; if (pct > 99) pct = 99; Resist[0] = Resist[0] * (100 - pct) / 100; Resist[1] = Resist[1] * (100 - pct) / 100; Resist[2] = Resist[2] * (100 - pct) / 100; Resist[3] = Resist[3] * (100 - pct) / 100; } Resist[0] -= decResist; Resist[1] -= decResist; Resist[2] -= decResist; Resist[3] -= decResist; } else if (Type == 4) //Poções { if (Value == 0) { DAMAGEMULTI += 3; tDamage += 20; MOBMagic += 10; } else if (Value == 1) { DAMAGEMULTI += 5; tDamage += 40; MOBMagic += 12; } else if (Value == 2) { DAMAGEMULTI += 6; tDamage += 60; MOBMagic += 16; } else if (Value == 3) { DAMAGEMULTI += 15; tDamage += 80; MOBMagic += 20; } } else if (Type == 5) //Luster_Furnish, Incapacitate { *BuffState |= FROZEN; } else if (Type == 6) //Divine_Protection (Proteção Divina), Absolute_Protection { *BuffState |= INVULNERABLE; } else if (Type == 7) //Freeze_Blade (Lâmina Congelante) { int scaledLevel = 4 * Level; int decAttSpeed = (scaledLevel / 10) + 10; attSpeed -= decAttSpeed; if (MOB->Equip[FACE].sIndex > 50) { int calcInt = MOB->CurrentScore.Int; calcInt -= (decAttSpeed + 10); MOB->CurrentScore.Int = calcInt; } } else if (Type == 8) //PvP Jewels { if ((Value & 2) != 0) //Joia da Resistencia { int incResist = 25; Resist[0] += incResist; Resist[1] += incResist; Resist[2] += incResist; Resist[3] += incResist; } if ((Value & 16) != 0) //Joia da Proteção (+HP +Def) { tMaxHp = (tMaxHp + (MOB->CurrentScore.Level / 2)) + ((MOB->CurrentScore.Ac * 20) / 100); } if ((Value & 32) != 0) //Joia do Poder (+Ataque +HP) { DAMAGEMULTI += 5; MOBMagic += 4; tMaxHp = (107 * tMaxHp) / 100; } if ((Value & 128) != 0) //Joia Magia (MP -> HP) { StatePlusHp = TRUE; } } else if (Type == 9) //Magic_Weapon (Arma Mágica) { int scaledLevel = 4 * Level; int incAttack = (scaledLevel / 3) + 15; DAMAGEMULTI += 5; if (MOB->Class == 1) { if ((MOB->LearnedSkill[0] & LEARN_20) != 0) //Arma Magica { DAMAGEMULTI += 15; incAttack *= 5; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) //8 Skill Magia Especial DAMAGEMULTI += 7; } // LearnedSkill[1] & 0x400 (nLEARN_10) — Special_Master (11th FM Especial): +5% buff dano if ((MOB->LearnedSkill[1] & nLEARN_10) != 0) { incAttack += incAttack / 20; DAMAGEMULTI += DAMAGEMULTI / 20; } } tDamage += incAttack; } else if (Type == 10) //Waste (Enfraquecimento) { int scaledLevel = 4 * Level; if (scaledLevel >= 255) { int pct = scaledLevel / 60; if (pct > 99) pct = 99; tDamage = tDamage * (100 - pct) / 100; } tDamage -= Value + scaledLevel / 5; } else if (Type == 11) //Magic_Shield (Escudo Mágico) { if (Value == 1) MOB->CurrentScore.Ac *= 2; else { int scaledLevel = 4 * Level; int incDef = Value + scaledLevel / 3; // FM class — percentage multiplier (100% base, 102% com LEARN_23, +5% com Special_Master 11th) if (MOB->Class == 1) { int acPct = 100; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) acPct = 102; if ((MOB->LearnedSkill[1] & nLEARN_10) != 0) acPct += 5; MOB->CurrentScore.Ac = acPct * MOB->CurrentScore.Ac / 100; } MOB->CurrentScore.Ac += incDef; } } else if (Type == 12) // { float incDef = (100 - Value) / 100.0; MOB->CurrentScore.Ac *= incDef; } else if (Type == 13) //Assault (Investida) { int calcBonus = MOB->CurrentScore.Special[2] / 10 + Value; DAMAGEMULTI += calcBonus; if ((MOB->LearnedSkill[0] & LEARN_15) != 0) //8 Skill Trans DAMAGEMULTI += 5; // LearnedSkill[1] bit 7 (nLEARN_07) — WindStorm (12th TK Trans): elimina HP penalty, +DMG if ((MOB->LearnedSkill[1] & nLEARN_07) == 0) HPMULTI -= 10; else DAMAGEMULTI += 2; } else if (Type == 14) //Growth (Crescimento/Samaritano) { int useLvl = MOB->CurrentScore.Level; if (BASE_IsTranscended(OriginalFace) == 1 && (MOB->LearnedSkill[0] & LEARN_30) != 0) useLvl += 400; int calcCon = Value + 6 * MOB->CurrentScore.Special[1] + 4 * useLvl / 3 + MOB->CurrentScore.Con; int Increase = 0; int incSpecial = 0; if ((MOB->LearnedSkill[0] & LEARN_07) != 0) //8 Skill Confianca { Increase = 400; incSpecial = 6 * MOB->CurrentScore.Special[1]; } MOB->CurrentScore.Dex += Increase; MOB->CurrentScore.Int += Increase; MOB->CurrentScore.Str += Increase; MOB->CurrentScore.Con = Increase + incSpecial + calcCon; } else if (Type == 15) //Skill_Amplify (Amplificação/Athena) { int scaledLevel = 4 * Level; int incSpecial = (scaledLevel / 10) + Value; if (MOB->Class == 1 && (MOB->LearnedSkill[0] & LEARN_23) != 0) incSpecial = 120 * incSpecial / 100; MOB->CurrentScore.Special[0] += incSpecial; MOB->CurrentScore.Special[1] += incSpecial; MOB->CurrentScore.Special[2] += incSpecial; MOB->CurrentScore.Special[3] += incSpecial; // Special_Master (11th FM Especial): +10% bonus a cada special if (MOB->Class == 1 && (MOB->LearnedSkill[1] & nLEARN_10) != 0) { MOB->CurrentScore.Special[0] = 110 * MOB->CurrentScore.Special[0] / 100; MOB->CurrentScore.Special[1] = 110 * MOB->CurrentScore.Special[1] / 100; MOB->CurrentScore.Special[2] = 110 * MOB->CurrentScore.Special[2] / 100; MOB->CurrentScore.Special[3] = 110 * MOB->CurrentScore.Special[3] / 100; } if (MOB->CurrentScore.Special[0] > MAX_SPECIAL_G) MOB->CurrentScore.Special[0] = MAX_SPECIAL_G; if (MOB->CurrentScore.Special[1] > MAX_SPECIAL_G) MOB->CurrentScore.Special[1] = MAX_SPECIAL_G; if (MOB->CurrentScore.Special[2] > MAX_SPECIAL_G) MOB->CurrentScore.Special[2] = MAX_SPECIAL_G; if (MOB->CurrentScore.Special[3] > MAX_SPECIAL_G) MOB->CurrentScore.Special[3] = MAX_SPECIAL_G; } else if (Type == 16) //Transform: Werewolf, Werebear, Astaroth, Titan, Evilator { int transfId = Value - 1; if (transfId > EDEN || MOB->Class != 2) continue; if (transfId == EDEN) MOB->Equip[FACE].sIndex = 32; else MOB->Equip[FACE].sIndex = transfId + 22; int faceId = MOB->Equip[FACE].sIndex; int damAdd = 0; int hpAdd = 0; int acAdd = 0; int attSpeedAdd = 0; int resistAdd = 0; if (faceId == 25) //Titan { resistAdd = pTransBonus[transfId].MinResist; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) acAdd = 10; } else if (faceId == 32) //Eden { attSpeedAdd = 10; resistAdd = pTransBonus[transfId].MinResist; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) { damAdd = 5; acAdd = 2; } } else if ((MOB->LearnedSkill[0] & LEARN_21) != 0) //Metamorfose Superior { resistAdd = pTransBonus[transfId].MinResist; if (faceId == 22) //Lobisomem { MOB->Critical = pTransBonus[transfId].MinCritical + ((MOB->CurrentScore.Special[3] / 50) * pTransBonus[transfId].MaxCritical); damAdd = 10; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) { damAdd = 15; acAdd = 2; } } else if (faceId == 23) //Homem Urso { hpAdd = 20; attSpeedAdd = 20; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) { damAdd = 5; acAdd = 2; } } else if (faceId == 24) //Astaroth { attSpeedAdd = 20; damAdd = 10; if ((MOB->LearnedSkill[0] & LEARN_23) != 0) { damAdd = 15; acAdd = 2; } } } int sancFace = 0; if (BASE_IsTranscended(OriginalFace) == 1 && (MOB->LearnedSkill[0] & LEARN_30) != 0) sancFace = 9; else { sancFace = ((MOB->CurrentScore.Special[3] + 2 * MOB->CurrentScore.Level) / 3 - pTransBonus[transfId].Sanc) / 12; if (sancFace < 0) sancFace = 0; if (sancFace > 9) sancFace = 9; } MOB->Equip[FACE].stEffect[0].cEffect = EF_SANC; MOB->Equip[FACE].stEffect[0].cValue = sancFace; int sp3 = MOB->CurrentScore.Special[3]; //Calculo do Dano DAMAGEMULTI = (DAMAGEMULTI + damAdd + pTransBonus[transfId].MinAttack + (sp3 * (pTransBonus[transfId].MaxAttack - pTransBonus[transfId].MinAttack) / 200)) - 100; //Calculo da Defesa int calcAc = MOB->CurrentScore.Ac * (acAdd + pTransBonus[transfId].MinDefense + sp3 * (pTransBonus[transfId].MaxDefense - pTransBonus[transfId].MinDefense) / 200) / 100; if (faceId == 22) calcAc += 5; MOB->CurrentScore.Ac = calcAc; //Calculo do Hp tMaxHp = tMaxHp * (hpAdd + pTransBonus[transfId].MinHp + sp3 * (pTransBonus[transfId].MaxHp - pTransBonus[transfId].MinHp) / 200) / 100; //Calculo das Resistencias Resist[0] += resistAdd; Resist[1] += resistAdd; Resist[2] += resistAdd; Resist[3] += resistAdd; //Calculo SpeedAttack AttackSpeedBonus = pTransBonus[transfId].SpeedAttack + attSpeedAdd; //Velocidade de Movimento RunSpeedBonus = pTransBonus[transfId].SpeedMove; } else if (Type == 18) //Mana_Control (Controle de Mana) { *BuffState |= MANACONTROL; } else if (Type == 19) //Immunity (Imunidade) { *BuffState |= CANCEL; } else if (Type == 21) //Transfer_Armor_Class (Meditação) { int sp1 = MOB->CurrentScore.Special[1]; MOB->CurrentScore.Ac += -10 - sp1 / 3; int incDmg = sp1 / 10 + Value; if ((MOB->LearnedSkill[0] & LEARN_07) != 0) incDmg += 5; DAMAGEMULTI += incDmg; } else if (Type == 24) //Rescue (Possuído/Berserker) { MOB->CurrentScore.Ac += 5 * MOB->CurrentScore.Special[2]; if (BASE_GetItemAbility(&MOB->Equip[SHIELD], EF_POS) == SHIELD_POS) { MOB->BerserkerResist += 10; Resist[0] += 20; Resist[1] += 20; Resist[2] += 20; Resist[3] += 20; } if ((MOB->LearnedSkill[0] & LEARN_15) != 0) MOB->BerserkerResist += 20; } else if (Type == 25) //Elemental_Protector (Proteção Elemental) { int sp2 = MOB->CurrentScore.Special[2]; int incResist = (sp2 / 4 + Value); if (sp2 >= 255) { int pctBoost = (sp2 - 255) / 15; if (pctBoost > 0) { Resist[0] = (pctBoost + 100) * Resist[0] / 100; Resist[1] = (pctBoost + 100) * Resist[1] / 100; Resist[3] = (pctBoost + 100) * Resist[3] / 100; } } Resist[0] += incResist / 10; Resist[1] += incResist / 10; Resist[3] += incResist / 10; } else if (Type == 26) //Dodge (Evasão Aprimorada) { *BuffState |= EVASION; } else if (Type == 27) //Enchant_Frost (Encantar Gelo) { *BuffState |= FREEZE; } else if (Type == 28) //Hide_In_Shadow (Invisibilidade) { *BuffState |= INVISIBLE; } else if (Type == 29) //Soul_Of_Limits (Limite da Alma) / Weapon_Mastery { // HT bonus +20% se Class==3 e LearnedSkill[1] bit 7 (nLEARN_07) int htBonus = 0; if (MOB->Class == 3 && (MOB->LearnedSkill[1] & nLEARN_07) != 0) htBonus = 20; int _nIncreese = 1; if (BASE_IsTranscended(OriginalFace) == 1) _nIncreese = 2; if ((MOB->LearnedSkill[0] & LEARN_30) != 0) { if (soultype < 1 || soultype > 10) soultype = 1; switch (soultype) { case 1: { MOB->CurrentScore.Str += MOB->CurrentScore.Str * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Con += MOB->CurrentScore.Con * (htBonus + 30 * _nIncreese) / 100; } break; case 2: { MOB->CurrentScore.Int += MOB->CurrentScore.Int * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Con += MOB->CurrentScore.Con * (htBonus + 30 * _nIncreese) / 100; } break; case 3: { MOB->CurrentScore.Dex += MOB->CurrentScore.Dex * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Con += MOB->CurrentScore.Con * (htBonus + 30 * _nIncreese) / 100; } break; case 4: { MOB->CurrentScore.Str += MOB->CurrentScore.Str * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Dex += MOB->CurrentScore.Dex * (htBonus + 30 * _nIncreese) / 100; } break; case 5: { MOB->CurrentScore.Int += MOB->CurrentScore.Int * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Dex += MOB->CurrentScore.Dex * (htBonus + 30 * _nIncreese) / 100; } break; case 6: { MOB->CurrentScore.Con += MOB->CurrentScore.Con * (htBonus + 40 * _nIncreese) / 100; MOB->CurrentScore.Dex += MOB->CurrentScore.Dex * (htBonus + 30 * _nIncreese) / 100; } break; case 7: { MOB->CurrentScore.Str += MOB->CurrentScore.Str * (htBonus + 60 * _nIncreese) / 100; } break; case 8: { MOB->CurrentScore.Int += MOB->CurrentScore.Int * (htBonus + 60 * _nIncreese) / 100; } break; case 9: { MOB->CurrentScore.Con += MOB->CurrentScore.Con * (htBonus + 60 * _nIncreese) / 100; } break; case 10: { MOB->CurrentScore.Dex += MOB->CurrentScore.Dex * (htBonus + 60 * _nIncreese) / 100; } break; } } else if (_nIncreese == 1) { MOB->CurrentScore.Str = 2000; MOB->CurrentScore.Int = 2000; tMaxHPb = 10000; } if (MOB->CurrentScore.Str >= MAX_STATSPOINT) MOB->CurrentScore.Str = MAX_STATSPOINT; if (MOB->CurrentScore.Int >= MAX_STATSPOINT) MOB->CurrentScore.Int = MAX_STATSPOINT; if (MOB->CurrentScore.Con >= MAX_STATSPOINT) MOB->CurrentScore.Con = MAX_STATSPOINT; if (MOB->CurrentScore.Dex >= MAX_STATSPOINT) MOB->CurrentScore.Dex = MAX_STATSPOINT; } else if (Type == 31) //Coin_Armor (Escudo Dourado) { int scaledLevel = 4 * Level; int incDef = (scaledLevel / 10) + Value; if ((MOB->LearnedSkill[0] & LEARN_15) != 0) incDef += 100; // Plunder (11th HT Troca): +10% AC no Escudo Dourado if ((MOB->LearnedSkill[1] & nLEARN_06) != 0) incDef += 10 * MOB->CurrentScore.Ac / 100; MOB->CurrentScore.Ac += incDef; } else if (Type == 33) //Scroll Transform (pergaminho) { int setFace = 0; if (Value == 0) setFace = 202; //Gremlin if (Value == 1) setFace = 209; //Arqueiro Orc if (Value == 2) setFace = 212; //Troll Machado if (Value == 3) setFace = 230; //Carbunkle if (Value == 4) setFace = 229; //Chefe Zumbi if (Value == 5) setFace = 216; //Javali Selvagem if (Value == 6) setFace = 226; //Lobo if (Value == 7) setFace = 298; //Golem Chefe MOB->Equip[FACE].sIndex = setFace; } else if (Type == 34) //Divine Potion { DAMAGEMULTI += 20; tDamage += 160; MOBMagic += 40; HPMULTI += 30; MPMULTI += 30; } else if (Type == 35) //Vigor Potion (sem skill — poção) { if (Affect[i].Time >= 1000000) { HPMULTI += 30; MPMULTI += 30; } else { HPMULTI += 10; MPMULTI += 10; } } else if (Type == 36) //Critical_Armor / Venom (Veneno) { *BuffState |= POISON; } else if (Type == 37) //Another_Soul (Ligação Espectral) { *AnotherSkill = 3 * (MOB->CurrentScore.Special[2] / 10 + 8); } else if (Type == 38) //Spirit_Change (Troca de Espírito) / Improved_Range { int useLvl = MOB->CurrentScore.Level; if (BASE_IsTranscended(OriginalFace) == 1) { if ((MOB->LearnedSkill[0] & LEARN_30) != 0) useLvl += 400; int mana = MOB->CurrentScore.MaxMp / 4; tMaxHp += mana + (MOB->CurrentScore.Special[2] / 2) + 2 * useLvl + 750; MOB->CurrentScore.MaxMp -= mana; } else { int mana = MOB->CurrentScore.MaxMp / 4 + (MOB->CurrentScore.Special[2] + useLvl) / 4; tMaxHp += mana + 750; MOB->CurrentScore.MaxMp -= mana; } } else if (Type == 41) //Flash / Clod_Attack { *BuffState |= FLASH; } else if (Type == 42) //Mystery_Magic (Magia Misteriosa) { MOBMagic += 20; } else if (Type == 45) //Ice_Binding (Aprisionamento de Gelo) { *BuffState |= 0x2000; } else if (Type == 48) //Last_Resistance (Última Resistência) { HPMULTI += Value; MOB->CurrentScore.Ac += Value * MOB->CurrentScore.Ac / 100; } else if (Type == 51) //HP buff (sem skill) { if (Value) tMaxHp += 5 * tMaxHp / 100; } else if (Type == 53) //TowerWarBuff (buff de Guerra de Torre / Siege) { if (Level >= 0 && Level < TOWERWAR_BUFF_ROWS) { const auto& row = g_pTowerWarBuff[Level]; for (int j = 0; j < TOWERWAR_BUFF_SLOTS; j++) { int bType = row.nType[j]; int bValue = row.nValue[j]; if (bType == 0 || bValue == 0) continue; switch (bType) { case 4: // HP flat bonus tMaxHp += bValue; break; case 26: // Attack Speed bonus attSpeed += bValue; break; case 54: // All Resistances bonus Resist[0] += bValue; Resist[1] += bValue; Resist[2] += bValue; Resist[3] += bValue; break; case 74: // All Specials bonus (cap MAX_SPECIAL_G) { int s0 = bValue + MOB->CurrentScore.Special[0]; int s1 = bValue + MOB->CurrentScore.Special[1]; int s2 = bValue + MOB->CurrentScore.Special[2]; int s3 = bValue + MOB->CurrentScore.Special[3]; if (s0 > MAX_SPECIAL_G) s0 = MAX_SPECIAL_G; if (s1 > MAX_SPECIAL_G) s1 = MAX_SPECIAL_G; if (s2 > MAX_SPECIAL_G) s2 = MAX_SPECIAL_G; if (s3 > MAX_SPECIAL_G) s3 = MAX_SPECIAL_G; MOB->CurrentScore.Special[0] = s0; MOB->CurrentScore.Special[1] = s1; MOB->CurrentScore.Special[2] = s2; MOB->CurrentScore.Special[3] = s3; } break; } } } } } if (face < 4) { tDamage += (MOB->CurrentScore.Str >> 1) + (MOB->CurrentScore.Dex / 12) + MOB->CurrentScore.Special[0]; if (faceindex % 10 <= 5) tDamage += MOB->CurrentScore.Level; else tDamage += (MOB->CurrentScore.Level * 2); } for (int i = 0; i <= 3; i++) { if (Resist[i] < 0) Resist[i] = 0; if (Resist[i] > MAX_RESIST_G) Resist[i] = MAX_RESIST_G; MOB->Resist[i] = (int16_t)Resist[i]; } if (DAMAGEMULTI != 100) tDamage = (tDamage * DAMAGEMULTI) / 100; if (HPMULTI != 100) { int PlusMaxHp = (tMaxHp * HPMULTI) / 100; if (PlusMaxHp > MAX_MAXHP_G) PlusMaxHp = MAX_MAXHP_G; tMaxHp = PlusMaxHp; } if (MPMULTI != 100) { int PlusMaxMp = (MOB->CurrentScore.MaxMp * MPMULTI) / 100; if (PlusMaxMp > MAX_MAXMP_G) PlusMaxMp = MAX_MAXMP_G; MOB->CurrentScore.MaxMp = PlusMaxMp; } if (StatePlusHp == TRUE) //Converte Metade do MP em HP { tMaxHp += MOB->CurrentScore.MaxMp / 2; MOB->CurrentScore.MaxMp /= 2; } if (regenHP < 0) regenHP = 0; if (regenHP > MAX_REGEN_G) regenHP = MAX_REGEN_G; MOB->RegenHP = (int16_t)regenHP; if (regenMP < 0) regenMP = 0; if (regenMP > MAX_REGEN_G) regenMP = MAX_REGEN_G; MOB->RegenMP = (int16_t)regenMP; movSpeed += RunSpeedBonus; if (movSpeed < 0) movSpeed = 0; if (movSpeed > MAX_MOVSPEED_G) movSpeed = MAX_MOVSPEED_G; int attFinal; if (face >= 4) { int mobAttSpeed = AttackSpeedBonus + MOB->CurrentScore.Dex / 5 + attSpeed; if (mobAttSpeed < 0) mobAttSpeed = 0; if (mobAttSpeed > MAX_MOB_ATTSPEED_G) mobAttSpeed = MAX_MOB_ATTSPEED_G; attFinal = (mobAttSpeed / 10) & 0xF; } else { attFinal = BASE_GetDiminishedAttackSpeed(MOB->CurrentScore.Dex) + attSpeed; } MOB->MultiHitThreshold = (int16_t)attFinal; MOB->CurrentScore.AttackRun = movSpeed + 16 * attFinal; int mobMerchant = MOB->Merchant; if (face >= 4) { if (MOB->Merchant >= 100 && MOB->Merchant < 200) mobMerchant = 8; } else { // Exp death penalty if (BASE_IsTranscended(OriginalFace) == 1 && (MOB->LearnedSkill[0] & LEARN_30) != 0) { long long curExp = g_pNextLevel_2[MOB->BaseScore.Level]; long long nextExp = g_pNextLevel_2[MOB->BaseScore.Level + 1]; int calcExp = (int)((nextExp - curExp) / 10); if (Ext1->Data[0] >= calcExp) MOB->CurrentScore.Ac /= 2; } else { long long curExp = g_pNextLevel[MOB->BaseScore.Level]; long long nextExp = g_pNextLevel[MOB->BaseScore.Level + 1]; int calcExp = (int)((nextExp - curExp) / 10); if (Ext1->Data[0] >= ((calcExp * 4) / 5)) MOB->CurrentScore.Ac /= 2; } } MOB->CurrentScore.Reserved = ((MOB->CurrentScore.Reserved & 240) | mobMerchant); MOB->SkillMagic = (int16_t)(MOBMagic / 4); if (tDamage > MAX_DAMAGE_G) tDamage = MAX_DAMAGE_G; MOB->CurrentScore.Damage = tDamage; if (tMaxHp > MAX_MAXHP_G) tMaxHp = MAX_MAXHP_G; MOB->CurrentScore.MaxHp = tMaxHp; if (MOB->CurrentScore.Hp > MOB->CurrentScore.MaxHp) MOB->CurrentScore.Hp = MOB->CurrentScore.MaxHp; if (MOB->CurrentScore.Mp > MOB->CurrentScore.MaxMp) MOB->CurrentScore.Mp = MOB->CurrentScore.MaxMp; } STRUCT_MOB_GLOBAL tem 1368 bytes STRUCT_ITEM_GLOBAL tem 12 bytes A maioria dos cálculos e bônus das skills estão ai, devidamente atualizados com a versão atual do Global.
- 8 respostas
-
- 33
-
-
-
É por ai, a primeira analise tem que ser feita manualmente, salvo se vc já conhece os padrões da função que esta procurasndo.
