From 1829eb0abb8740f2db5b7fc497b7483372b5b6a0 Mon Sep 17 00:00:00 2001 From: YeLike <93629620+YeLikesss@users.noreply.github.com> Date: Sun, 9 Jun 2024 21:43:20 +0800 Subject: [PATCH] =?UTF-8?q?[=E5=8A=9F=E8=83=BD]=20CxdecStringDumper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CxdecExtractorLoader/CxdecExtractorLoader.cpp | 169 +++++++++++---- CxdecExtractorLoader/CxdecExtractorLoader.rc | Bin 3238 -> 5402 bytes .../CxdecExtractorLoader.vcxproj | 32 ++- .../CxdecExtractorLoader.vcxproj.filters | 51 +++++ CxdecExtractorLoader/resource.h | 10 +- CxdecStringDumper/Application.cpp | 149 +++++++++++++ CxdecStringDumper/Application.h | 69 ++++++ CxdecStringDumper/CxdecStringDumper.vcxproj | 190 ++++++++++++++++ .../CxdecStringDumper.vcxproj.filters | 112 ++++++++++ CxdecStringDumper/ExtendUtils.h | 39 ++++ CxdecStringDumper/HashCore.cpp | 205 ++++++++++++++++++ CxdecStringDumper/HashCore.h | 201 +++++++++++++++++ CxdecStringDumper/dllmain.cpp | 28 +++ KrkrZCxdecV2.sln | 6 + README.md | 44 ++-- 15 files changed, 1244 insertions(+), 61 deletions(-) create mode 100644 CxdecStringDumper/Application.cpp create mode 100644 CxdecStringDumper/Application.h create mode 100644 CxdecStringDumper/CxdecStringDumper.vcxproj create mode 100644 CxdecStringDumper/CxdecStringDumper.vcxproj.filters create mode 100644 CxdecStringDumper/ExtendUtils.h create mode 100644 CxdecStringDumper/HashCore.cpp create mode 100644 CxdecStringDumper/HashCore.h create mode 100644 CxdecStringDumper/dllmain.cpp diff --git a/CxdecExtractorLoader/CxdecExtractorLoader.cpp b/CxdecExtractorLoader/CxdecExtractorLoader.cpp index 314f811..01325f0 100644 --- a/CxdecExtractorLoader/CxdecExtractorLoader.cpp +++ b/CxdecExtractorLoader/CxdecExtractorLoader.cpp @@ -1,62 +1,157 @@ #include #include -#include -#pragma comment(lib, "shlwapi.lib") + +#include "path.h" +#include "util.h" +#include "directory.h" +#include "encoding.h" +#include "resource.h" #pragma comment(linker, "/MERGE:\".detourd=.data\"") #pragma comment(linker, "/MERGE:\".detourc=.rdata\"") -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) -{ - UNREFERENCED_PARAMETER(hPrevInstance); - constexpr size_t MaxPath = 1024u; +#ifdef _UNICODE +#if defined _M_IX86 +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_X64 +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#else +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif +#endif - wchar_t appFullPathW[MaxPath]; - wchar_t gameFullPathW[MaxPath]; - GetModuleFileNameW(hInstance, appFullPathW, MaxPath); - int argc = 0; - LPWSTR* argv = CommandLineToArgvW(lpCmdLine, &argc); - if (argc) - { - wchar_t* arg = argv[0]; - memcpy(gameFullPathW, arg, (lstrlenW(arg) + 1) * 2); +static std::wstring g_LoaderFullPath; //加载器全路径 +static std::wstring g_LoaderCurrentDirectory; //加载器目录 +static std::wstring g_KrkrExeFullPath; //Krkr游戏主程序全路径 +static std::wstring g_KrkrExeDirectory; //Krkr游戏目录 - if (lstrcmpW(appFullPathW, gameFullPathW)) +/// +/// 主窗体消息循环 +/// +/// 窗口句柄 +/// 消息 +/// +/// +INT_PTR CALLBACK LoaderDialogWindProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_COMMAND: { - wchar_t gameCurrentDirectory[MaxPath]; - char injectDllFullPathA[MaxPath]; - + std::wstring injectDllFileName = std::wstring(); + switch (LOWORD(wParam)) { - memcpy(gameCurrentDirectory, gameFullPathW, MaxPath); - *(PathFindFileNameW(gameCurrentDirectory) - 1u) = L'\0'; + case IDC_Extractor: + { + injectDllFileName = L"CxdecExtractorUI.dll"; + break; + } + case IDC_StringDumper: + { + injectDllFileName = L"CxdecStringDumper.dll"; + break; + } + case IDC_KeyDumper: + { + //NotImpl + ::MessageBoxW(nullptr, L"此功能暂未实现", L"错误", MB_OK); + break; + } } + + if (!injectDllFileName.empty()) { - constexpr const char InjectDllNameA[] = "CxdecExtractorUI.dll"; - GetModuleFileNameA(hInstance, injectDllFullPathA, MaxPath); - char* dllName = PathFindFileNameA(injectDllFullPathA); - memcpy(dllName, InjectDllNameA, sizeof(InjectDllNameA)); - } + std::wstring injectDllFullPath = Path::Combine(g_LoaderCurrentDirectory, injectDllFileName); + std::string injectDllFullPathA = Encoding::UnicodeToAnsi(injectDllFullPath, Encoding::CodePage::ACP); - STARTUPINFO si{ 0 }; - si.cb = sizeof(si); - PROCESS_INFORMATION pi{ 0 }; + STARTUPINFOW si{ }; + si.cb = sizeof(si); + PROCESS_INFORMATION pi{ }; - if (DetourCreateProcessWithDllW(gameFullPathW, NULL, NULL, NULL, FALSE, 0, NULL, gameCurrentDirectory, &si, &pi, injectDllFullPathA, NULL)) - { - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); + if (DetourCreateProcessWithDllW(g_KrkrExeFullPath.c_str(), NULL, NULL, NULL, FALSE, 0u, NULL, g_KrkrExeDirectory.c_str(), &si, &pi, injectDllFullPathA.c_str(), NULL)) + { + ::CloseHandle(pi.hThread); + ::CloseHandle(pi.hProcess); + + //运行成功 关闭窗口 + ::PostMessageW(hwnd, WM_CLOSE, 0u, 0u); + } + else + { + ::MessageBoxW(nullptr, L"创建进程错误", L"错误", MB_OK); + } } - else + return TRUE; + } + case WM_CLOSE: + { + ::DestroyWindow(hwnd); + return TRUE; + } + case WM_DESTROY: + { + ::PostQuitMessage(0); + return TRUE; + } + } + return FALSE; +} + + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + + std::wstring loaderFullPath = Util::GetAppPathW(); + std::wstring loaderCurrentDirectory = Path::GetDirectoryName(loaderFullPath); + std::wstring krkrExeFullPath = std::wstring(); + std::wstring krkrExeDirectory = std::wstring(); + + //获取启动参数 + { + int argc = 0; + LPWSTR* argv = ::CommandLineToArgvW(lpCmdLine, &argc); + if (argc) + { + wchar_t* arg = argv[0]; + + krkrExeFullPath = std::wstring(arg); + krkrExeDirectory = Path::GetDirectoryName(krkrExeFullPath); + } + ::LocalFree(argv); + } + + g_LoaderFullPath = loaderFullPath; + g_LoaderCurrentDirectory = loaderCurrentDirectory; + g_KrkrExeFullPath = krkrExeFullPath; + g_KrkrExeDirectory = krkrExeDirectory; + + //启动加载器 + { + if (!krkrExeFullPath.empty() && krkrExeFullPath != loaderFullPath) + { + HWND hwnd = ::CreateDialogParamW((HINSTANCE)hInstance, MAKEINTRESOURCEW(IDD_MainForm), NULL, LoaderDialogWindProc, 0u); + ::ShowWindow(hwnd, SW_NORMAL); + + MSG msg{ }; + while (BOOL ret = ::GetMessageW(&msg, NULL, 0u, 0u)) { - MessageBoxW(nullptr, L"创建进程错误", L"错误", MB_OK); + if (ret == -1) + { + return -1; + } + else + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } } } else { - MessageBoxW(nullptr, L"请拖拽游戏主程序到启动器", L"错误", MB_OK); + ::MessageBoxW(nullptr, L"请拖拽游戏主程序到启动器", L"错误", MB_OK); } } - LocalFree(argv); return 0; } diff --git a/CxdecExtractorLoader/CxdecExtractorLoader.rc b/CxdecExtractorLoader/CxdecExtractorLoader.rc index 8c9ddca689a4116554c1afc311b4b30416762cfa..2bd1feb3bc62cb4570ef3b2b933f30634c7d4959 100644 GIT binary patch literal 5402 zcmd^D%Wm6N5S>jnMe`2~4HOCL*ox!C$YHiDqtTaJdpt~Zg z?6M5<1^J8a+f9F<-;u{Tb2%X;lCJBb0D*#_hIcr3?!0HHPal_MSv+aUQ2H{KkxXz8 z40^HwyUg2o04t5;82X>VIm)1XEBq=MaLAjVk8Zq_vK>4EN^_v1x-3nZic@;xh}uFjJCi2bTj<0dn3ow zlT_Xe)h5?w^5;;l!%9s=j?0?}IkJNcsv&lIh@dT8kqF;W|#Zpb2dN1&_XG`DA-f;k)@hufeCAa8(O^*P;&z0Q+%q1l5k zyUOL$v^3^lgGHHb?+JO|f(?sm0N+=k`-WBruDtllueJwLi7RdcmP_o+T5_2)@~XTq z*&5@^0czC>7DoM9LzGZeLQOe|pY>*5E@_Mb;H}(QA1%CL3+$qqLvP^-KvVkwFiU z(np6m`GQI7ki7NOi|j!jdiV${_I}O_0piA!XF4xTwL_hP;;~KyoD$f<$1&|0c6w@I zP8jSu9w<+79f7K^v&B%Q@pPS*%XPP7&~TENV5gINf;A;!@1#83L#{`-IFY=F-yQ?H zen^P})UZ~}%Pe62o`(Ud9cPSre9_uRZ99fM)OH_w{s0Mk$lIk@r)0)4FO(}rOd6BU zpa;7R)v_bZgN7yKO{>;ZR&7H2u}&_u_B!hMwswbyuwz9#f%{#NyRZ#A!+kuF6Q{g= z&~vJ(W5u4(0IsQC=mN_g<=(V_t1I^=I@3CQ%3aBO_P0=LStrKtDaSVUwb6;%GuRq9 zv_Z}JlKv(iN6*PJQtlykQObE*ADHLbIr}*CIlRwVxvx<$hBhOt*ty1W%}cbouiE?a zzBhXP+nYb$`nq0)mC|;qfakr~LishG`b6lI#+%=)L0cZ!%gRXduDtC)Q$+kYqH1Dy9FS2p)1PpZ3@u-t4HW|UbjmJgbA z2APqZ@&aUeh~H52)!q{F=7cba=gW)rRL+%*(}qrjZjvlMD?c-eY7?tYx;jxpi{5-r z_LkMDiI{I8`Z*C(UvgWU$pEA7s8u#|=vU+paw>hU?b*c%9qdT-cl=$AG%ADn)VEME z?&NZJ*?KYQ`91CGNh_o8J=BG?^hpFKPyfG}%D%MHvDHKTuA__ayQ`}dk9m_EVW#jk?wKSWu-lhbFghKq^- delta 34 qcmbQGwM=pY8z-kHgENCagCB$8WJgZf$vT`dlcm_DHj4?dFarROTnBUj diff --git a/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj b/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj index cf1f330..ccce24a 100644 --- a/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj +++ b/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj @@ -47,14 +47,14 @@ true false - false - false + true + true false false - false - false + true + true false false @@ -63,11 +63,11 @@ Level3 _WIN32_WINNT=0x601;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true - Default + stdcpp20 false false AdvancedVectorExtensions2 - $(SolutionDir)Detours;%(AdditionalIncludeDirectories) + $(SolutionDir)Detours;$(SolutionDir)Common;%(AdditionalIncludeDirectories) MultiThreadedDebug false false @@ -108,11 +108,11 @@ true _WIN32_WINNT=0x601;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true - Default + stdcpp20 false false AdvancedVectorExtensions2 - $(SolutionDir)Detours;%(AdditionalIncludeDirectories) + $(SolutionDir)Detours;$(SolutionDir)Common;%(AdditionalIncludeDirectories) MultiThreaded false false @@ -154,6 +154,14 @@ + + + + + + + + @@ -162,6 +170,14 @@ + + + + + + + + diff --git a/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj.filters b/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj.filters index fc6942e..6d5cd96 100644 --- a/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj.filters +++ b/CxdecExtractorLoader/CxdecExtractorLoader.vcxproj.filters @@ -8,6 +8,9 @@ {1a3862fc-2e9c-40b5-9852-dc494c9ce530} + + {79160863-3116-4b56-8ddc-4a8907257830} + @@ -28,12 +31,60 @@ Detours + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + Detours + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + diff --git a/CxdecExtractorLoader/resource.h b/CxdecExtractorLoader/resource.h index eaa60ae..a246187 100644 --- a/CxdecExtractorLoader/resource.h +++ b/CxdecExtractorLoader/resource.h @@ -2,15 +2,19 @@ // Microsoft Visual C++ 生成的包含文件。 // 供 CxdecExtractorLoader.rc 使用 // -#define IDI_ICON1 101 +#define IDI_MainIcon 101 +#define IDD_MainForm 102 +#define IDC_Extractor 1001 +#define IDC_StringDumper 1002 +#define IDC_KeyDumper 1003 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_RESOURCE_VALUE 104 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_CONTROL_VALUE 1004 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/CxdecStringDumper/Application.cpp b/CxdecStringDumper/Application.cpp new file mode 100644 index 0000000..986816d --- /dev/null +++ b/CxdecStringDumper/Application.cpp @@ -0,0 +1,149 @@ +#include "Application.h" +#include "path.h" +#include "util.h" +#include "ExtendUtils.h" + +namespace Engine +{ + /// + /// 单实例 + /// + static Application* g_Instance = nullptr; + + //Hook插件功能 + tTVPV2LinkProc g_V2Link = nullptr; + HRESULT __stdcall HookV2Link(iTVPFunctionExporter* exporter) + { + HRESULT result = g_V2Link(exporter); + HookUtils::InlineHook::UnHook(g_V2Link, HookV2Link); + g_V2Link = nullptr; + + //初始化插件 + Application::GetInstance()->InitializeTVPEngine(exporter); + + return result; + } + + //Hook插件加载 + auto g_GetProcAddressFunction = ::GetProcAddress; + FARPROC WINAPI HookGetProcAddress(HMODULE hModule, LPCSTR lpProcName) + { + FARPROC result = g_GetProcAddressFunction(hModule, lpProcName); + if (result) + { + // 忽略序号导出 + if (HIWORD(lpProcName) != 0) + { + if (strcmp(lpProcName, "V2Link") == 0) + { + //Nt头偏移 + PIMAGE_NT_HEADERS ntHeader = PIMAGE_NT_HEADERS((ULONG_PTR)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew); + //可选头大小 + DWORD optionalHeaderSize = ntHeader->FileHeader.SizeOfOptionalHeader; + //第一个节表(代码段) + PIMAGE_SECTION_HEADER codeSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)ntHeader + sizeof(ntHeader->Signature) + sizeof(IMAGE_FILE_HEADER) + optionalHeaderSize); + + DWORD codeStartRva = codeSectionHeader->VirtualAddress; //代码段起始RVA + DWORD codeSize = codeSectionHeader->SizeOfRawData; //代码段大小 + + ULONG_PTR codeStartVa = (ULONG_PTR)hModule + codeStartRva; //代码段起始VA + + //初始化 + Application* app = Application::GetInstance(); + if (!app->IsTVPEngineInitialize()) + { + g_V2Link = (tTVPV2LinkProc)result; + HookUtils::InlineHook::Hook(g_V2Link, HookV2Link); + } + + //HashStringDumper接口 + HashCore* dumper = app->GetStringDumper(); + if (!dumper->IsInitialized()) + { + dumper->Initialize((PVOID)codeStartVa, codeSize); + } + + //初始化完毕 解除Hook + if (dumper->IsInitialized()) + { + HookUtils::InlineHook::UnHook(g_GetProcAddressFunction, HookGetProcAddress); + } + } + } + } + return result; + } + + + //**********Application***********// + Application::Application() + { + this->mCurrentDirectoryPath = Path::GetDirectoryName(Util::GetModulePathW(::GetModuleHandleW(NULL))); + this->mTVPExporterInitialized = false; + + //单例Dumper + this->mStringDumper = HashCore::GetInstance(); + + //设置解包输出路径 + this->mStringDumper->SetOutputDirectory(this->mCurrentDirectoryPath); + } + + Application::~Application() + { + if (this->mStringDumper) + { + //单例Dumper释放 + HashCore::Release(); + this->mStringDumper = nullptr; + } + } + + void Application::InitializeModule(HMODULE hModule) + { + this->mModuleDirectoryPath = Path::GetDirectoryName(Util::GetModulePathW(hModule)); + } + + void Application::InitializeTVPEngine(iTVPFunctionExporter* exporter) + { + this->mTVPExporterInitialized = TVPInitImportStub(exporter); + TVPSetCommandLine(L"-debugwin", L"yes"); + } + + bool Application::IsTVPEngineInitialize() + { + return this->mTVPExporterInitialized; + } + + HashCore* Application::GetStringDumper() + { + return this->mStringDumper; + } + + //********====Static====********// + + Application* Application::GetInstance() + { + return g_Instance; + } + + void Application::Initialize(HMODULE hModule) + { + g_Instance = new Application(); + g_Instance->InitializeModule(hModule); + + //Hook + HookUtils::InlineHook::Hook(g_GetProcAddressFunction, HookGetProcAddress); + } + + void Application::Release() + { + if (g_Instance) + { + delete g_Instance; + g_Instance = nullptr; + } + } + + //================================// +} + diff --git a/CxdecStringDumper/Application.h b/CxdecStringDumper/Application.h new file mode 100644 index 0000000..3ba0e59 --- /dev/null +++ b/CxdecStringDumper/Application.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include "HashCore.h" + +namespace Engine +{ + using tTVPV2LinkProc = HRESULT(__stdcall*)(iTVPFunctionExporter*); + using tTVPV2UnlinkProc = HRESULT(__stdcall*)(); + + class Application + { + private: + Application(); + Application(const Application&) = delete; + Application(Application&&) = delete; + Application& operator=(const Application&) = delete; + Application& operator=(Application&&) = delete; + ~Application(); + + private: + + std::wstring mModuleDirectoryPath; //dll目录 + std::wstring mCurrentDirectoryPath; //游戏当前目录 + HashCore* mStringDumper; //Hash字符串dump + bool mTVPExporterInitialized; //插件初始化成功标志 + + public: + + /// + /// 设置模块信息 + /// + /// 模块信息 + void InitializeModule(HMODULE hModule); + + /// + /// 初始化插件 + /// + /// 插件导出函数 + void InitializeTVPEngine(iTVPFunctionExporter* exporter); + + /// + /// 获取插件是否初始化完毕 + /// + /// True已初始化 False未初始化 + bool IsTVPEngineInitialize(); + + /// + /// 获取解包器 + /// + /// + HashCore* GetStringDumper(); + + /// + /// 获取对象实例 + /// + static Application* GetInstance(); + /// + /// 初始化 + /// + /// 模块信息 + static void Initialize(HMODULE hModule); + /// + /// 释放 + /// + static void Release(); + }; +} + diff --git a/CxdecStringDumper/CxdecStringDumper.vcxproj b/CxdecStringDumper/CxdecStringDumper.vcxproj new file mode 100644 index 0000000..c8b85ad --- /dev/null +++ b/CxdecStringDumper/CxdecStringDumper.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + 16.0 + Win32Proj + {3adcce05-606f-4818-833d-e0e78230e8b0} + CxdecStringDumper + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + false + false + false + + + false + false + false + false + + + + Level3 + false + _WIN32_WINNT=0x601;WIN32;_DEBUG;CXDECSTRINGDUMPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp20 + false + false + false + false + AdvancedVectorExtensions2 + false + false + false + false + false + false + true + $(SolutionDir)Common;$(SolutionDir)KrkrPlugin;$(SolutionDir)Detours;%(AdditionalIncludeDirectories) + MultiThreadedDebug + false + false + false + false + + + Windows + true + false + false + false + false + false + true + false + false + false + false + + + false + + + + + Level3 + true + true + false + _WIN32_WINNT=0x601;WIN32;NDEBUG;CXDECSTRINGDUMPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp20 + false + false + false + false + AdvancedVectorExtensions2 + false + false + false + false + false + false + false + false + false + MultiThreaded + $(SolutionDir)Common;$(SolutionDir)KrkrPlugin;$(SolutionDir)Detours;%(AdditionalIncludeDirectories) + false + false + + + Windows + true + true + false + false + false + false + false + false + false + false + false + true + false + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CxdecStringDumper/CxdecStringDumper.vcxproj.filters b/CxdecStringDumper/CxdecStringDumper.vcxproj.filters new file mode 100644 index 0000000..0d2dc0a --- /dev/null +++ b/CxdecStringDumper/CxdecStringDumper.vcxproj.filters @@ -0,0 +1,112 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {31cfe412-f303-4fd8-917a-2f1b7fd82798} + + + {49ec2efa-ba13-4ca5-9527-c5e8b8e5ea1e} + + + {50a1afa9-bab3-4a98-ab88-687f26d688d5} + + + + + 源文件 + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Detours + + + Detours + + + Detours + + + Detours + + + Detours + + + KrkrPlugin + + + 源文件 + + + 源文件 + + + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Detours + + + KrkrPlugin + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/CxdecStringDumper/ExtendUtils.h b/CxdecStringDumper/ExtendUtils.h new file mode 100644 index 0000000..95fd1d8 --- /dev/null +++ b/CxdecStringDumper/ExtendUtils.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "detours.h" +namespace Engine +{ + namespace HookUtils + { + class InlineHook + { + public: + InlineHook() = delete; + InlineHook(const InlineHook&) = delete; + InlineHook(InlineHook&&) = delete; + InlineHook& operator=(const InlineHook&) = delete; + InlineHook& operator=(InlineHook&&) = delete; + ~InlineHook() = delete; + + + template + static void Hook(T& OriginalFunction, T DetourFunction) + { + DetourUpdateThread(GetCurrentThread()); + DetourTransactionBegin(); + DetourAttach(&(PVOID&)OriginalFunction, (PVOID&)DetourFunction); + DetourTransactionCommit(); + } + + template + static void UnHook(T& OriginalFunction, T DetourFunction) + { + DetourUpdateThread(GetCurrentThread()); + DetourTransactionBegin(); + DetourDetach(&(PVOID&)OriginalFunction, (PVOID&)DetourFunction); + DetourTransactionCommit(); + } + }; + } +} diff --git a/CxdecStringDumper/HashCore.cpp b/CxdecStringDumper/HashCore.cpp new file mode 100644 index 0000000..0aa5c29 --- /dev/null +++ b/CxdecStringDumper/HashCore.cpp @@ -0,0 +1,205 @@ +#include "HashCore.h" +#include "pe.h" +#include "file.h" +#include "directory.h" +#include "path.h" +#include "stringhelper.h" +#include "ExtendUtils.h" + +namespace Engine +{ + //**********IStringHasher***********// + tjs_int IStringHasher::GetSaltLength() const + { + return this->mSaltSize; + } + + const tjs_uint8* IStringHasher::GetSaltBytes() const + { + return this->mSalt; + } + + IStringHasher::VptrTable* IStringHasher::GetVptrTable() + { + return *(IStringHasher::VptrTable**)this; + } + + void IStringHasher::SetVptrTable(const VptrTable* vt) + { + *(const IStringHasher::VptrTable**)this = vt; + } + //================================// + + + //*****************Dumper******************// + + static HashCore* g_Instance = nullptr; //单实例 + HashCore::tCreateCompoundStorageMedia g_CreateStorageMediaFunc = nullptr; //TVPStorageMedia[Cxdec]接口 + + const CompoundStorageMedia* g_StorageMedia = nullptr; //封包管理媒体 + const IStringHasher::VptrTable* g_PathNameHasherOriginalVtPtr = nullptr; //文件夹路径Hash原虚表指针 + const IStringHasher::VptrTable* g_FileNameHasherOriginalVtPtr = nullptr; //文件名Hash原虚表指针 + + IStringHasher::VptrTable g_PathNameHasherHookVt; //文件夹路径Hash Hook虚表 + IStringHasher::VptrTable g_FileNameHasherHookVt; //文件名Hash Hook虚表 + + tjs_int __fastcall HookPathNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed); + tjs_int __fastcall HookFileNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed); + + //创建Media Hook + tjs_error __cdecl HookCreateCompoundStorageMedia(CompoundStorageMedia** retTVPStorageMedia, tTJSVariant* tjsVarPrefix, int argc, void* argv) + { + tjs_error result = g_CreateStorageMediaFunc(retTVPStorageMedia, tjsVarPrefix, argc, argv); + if (TJS_SUCCEEDED(result)) + { + //Unhook + HookUtils::InlineHook::UnHook(g_CreateStorageMediaFunc, HookCreateCompoundStorageMedia); + + //获取媒体对象 + CompoundStorageMedia* storageMedia = *retTVPStorageMedia; + g_StorageMedia = storageMedia; + + //打印Hash参数 + { + HashCore* dumper = g_Instance; + Log::Logger& uniLogger = dumper->mUniversalLogger; + + uniLogger.WriteUnicode(L"Hash Seed:%s\r\n", storageMedia->HasherSeed.c_str()); + uniLogger.WriteUnicode(L"PathNameHasherSalt:%s\r\n", StringHelper::BytesToHexStringW(storageMedia->PathNameHasher->GetSaltBytes(), storageMedia->PathNameHasher->GetSaltLength()).c_str()); + uniLogger.WriteUnicode(L"FileNameHasherSalt:%s\r\n", StringHelper::BytesToHexStringW(storageMedia->FileNameHasher->GetSaltBytes(), storageMedia->FileNameHasher->GetSaltLength()).c_str()); + } + + //文件夹路径Hash虚表Hook + { + IStringHasher::VptrTable* pnHasherVt = storageMedia->PathNameHasher->GetVptrTable(); + g_PathNameHasherOriginalVtPtr = pnHasherVt; + + g_PathNameHasherHookVt = *pnHasherVt; + g_PathNameHasherHookVt.Calculate = HookPathNameHasherCalcute; + storageMedia->PathNameHasher->SetVptrTable(&g_PathNameHasherHookVt); + } + + //文件名Hash虚表Hook + { + IStringHasher::VptrTable* fnHasherVt = storageMedia->FileNameHasher->GetVptrTable(); + g_FileNameHasherOriginalVtPtr = fnHasherVt; + + g_FileNameHasherHookVt = *fnHasherVt; + g_FileNameHasherHookVt.Calculate = HookFileNameHasherCalcute; + storageMedia->FileNameHasher->SetVptrTable(&g_FileNameHasherHookVt); + } + } + return result; + } + + //文件夹路径Hash计算Hook fastcall模拟thiscall + tjs_int __fastcall HookPathNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed) + { + tjs_int len = g_PathNameHasherOriginalVtPtr->Calculate(thisObj, nullptr, hashValueRet, str, seed); + + const wchar_t* relativeDirPath = str->c_str(); + //空文件夹替换 + if (*relativeDirPath == L'\0') + { + relativeDirPath = L"%EmptyString%"; + } + + tTJSVariantOctet* hashValue = hashValueRet->AsOctetNoAddRef(); + //打印 String[Sign]Hash[NewLine] + g_Instance->mDirectoryHashLogger.WriteUnicode(L"%s%s%s\r\n", relativeDirPath, HashCore::Split, StringHelper::BytesToHexStringW(hashValue->GetData(), hashValue->GetLength()).c_str()); + + return len; + } + + //文件名Hash计算Hook fastcall模拟thiscall + tjs_int __fastcall HookFileNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed) + { + tjs_int len = g_FileNameHasherOriginalVtPtr->Calculate(thisObj, nullptr, hashValueRet, str, seed); + + const wchar_t* fileName = str->c_str(); + + tTJSVariantOctet* hashValue = hashValueRet->AsOctetNoAddRef(); + //打印 String[Sign]Hash[NewLine] + g_Instance->mFileNameHashLogger.WriteUnicode(L"%s%s%s\r\n", fileName, HashCore::Split, StringHelper::BytesToHexStringW(hashValue->GetData(), hashValue->GetLength()).c_str()); + + return len; + } + + //================================// + + + //**********HashCore***********// + HashCore::HashCore() + { + } + + HashCore::~HashCore() + { + } + + void HashCore::SetOutputDirectory(const std::wstring& directory) + { + std::wstring dumpOutDirectory = Path::Combine(directory, HashCore::FolderName); + this->mDumperDirectoryPath = dumpOutDirectory; + + //创建输出目录 + Directory::Create(dumpOutDirectory); + + //日志初始化 + std::wstring directoryHashLogPath = Path::Combine(dumpOutDirectory, HashCore::DirectoryHashFileName); + std::wstring fileNameHashLogPath = Path::Combine(dumpOutDirectory, HashCore::FileNameHashFileName); + std::wstring universalLogPath = Path::Combine(dumpOutDirectory, HashCore::UniversalFileName); + File::Delete(directoryHashLogPath); + File::Delete(fileNameHashLogPath); + File::Delete(universalLogPath); + this->mDirectoryHashLogger.Open(directoryHashLogPath.c_str()); + this->mFileNameHashLogger.Open(fileNameHashLogPath.c_str()); + this->mUniversalLogger.Open(universalLogPath.c_str()); + + //写UTF-16LE bom头 + { + WORD bom = 0xFEFF; + this->mDirectoryHashLogger.WriteData(&bom, sizeof(bom)); + this->mFileNameHashLogger.WriteData(&bom, sizeof(bom)); + this->mUniversalLogger.WriteData(&bom, sizeof(bom)); + } + } + + void HashCore::Initialize(PVOID codeVa, DWORD codeSize) + { + PVOID createMedia = PE::SearchPattern(codeVa, codeSize, HashCore::CreateCompoundStorageMediaSignature, sizeof(HashCore::CreateCompoundStorageMediaSignature) - 1); + if (createMedia) + { + g_CreateStorageMediaFunc = (tCreateCompoundStorageMedia)createMedia; + + //Hook创建媒体接口 + HookUtils::InlineHook::Hook(g_CreateStorageMediaFunc, HookCreateCompoundStorageMedia); + } + } + + bool HashCore::IsInitialized() + { + return g_CreateStorageMediaFunc != nullptr; + } + + //************=====Static=====************// + + HashCore* HashCore::GetInstance() + { + if (g_Instance == nullptr) + { + g_Instance = new HashCore(); + } + return g_Instance; + } + + void HashCore::Release() + { + if (g_Instance) + { + delete g_Instance; + g_Instance = nullptr; + } + } + //================================// +} diff --git a/CxdecStringDumper/HashCore.h b/CxdecStringDumper/HashCore.h new file mode 100644 index 0000000..305c0b5 --- /dev/null +++ b/CxdecStringDumper/HashCore.h @@ -0,0 +1,201 @@ +#pragma once + +#include +#include +#include "tp_stub.h" +#include "log.h" + +namespace Engine +{ + + /// + /// Hash接口 + /// + class IStringHasher + { + public: + //IStringHasher::Calculate接口 fastcall模拟thiscall + using tCalculate = tjs_int(__fastcall*)(IStringHasher* thisObj, void* unuseEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed); + + /// + /// 虚表结构 + /// + struct VptrTable + { + void* Destruct; //析构 + tCalculate Calculate; //计算Hash + }; + + private: + //vtable offset:0x00 + tjs_uint8* mSalt; //盐指针 offset:0x04 + tjs_int mSaltSize; //盐大小 offset:0x08 + public: + virtual ~IStringHasher() = 0; + /// + /// Hash计算 + /// + /// 返回值 + /// 字符串 + /// 种子 + /// Hash长度 + virtual tjs_int Calculate(tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed) = 0; + + public: + /// + /// 获取盐的长度 + /// + tjs_int GetSaltLength() const; + /// + /// 获取盐数据指针 + /// + const tjs_uint8* GetSaltBytes() const; + + /// + /// 获取虚表指针 (Hook用) + /// + VptrTable* GetVptrTable(); + /// + /// 设置虚表指针 (Hook用) + /// + void SetVptrTable(const VptrTable* vt); + + IStringHasher() = delete; + IStringHasher(const IStringHasher&) = delete; + IStringHasher(IStringHasher&&) = delete; + IStringHasher& operator=(const IStringHasher&) = delete; + IStringHasher& operator=(IStringHasher&&) = delete; + }; + + /// + /// 文件路径Hash接口 + /// + class PathNameHasher : public IStringHasher + { + private: + //IStringHasher Base offset:0x00 + tjs_uint8 mSaltData[0x10]; //盐数据 offset:0x0C + + public: + PathNameHasher() = delete; + PathNameHasher(const PathNameHasher&) = delete; + PathNameHasher(PathNameHasher&&) = delete; + PathNameHasher& operator=(const PathNameHasher&) = delete; + PathNameHasher& operator=(PathNameHasher&&) = delete; + }; + + /// + /// 文件名Hash接口 + /// + class FileNameHasher : public IStringHasher + { + private: + //IStringHasher Base offset:0x00 + tjs_uint8 mSaltData[0x20]; //盐数据 offset:0x0C + + public: + FileNameHasher() = delete; + FileNameHasher(const FileNameHasher&) = delete; + FileNameHasher(FileNameHasher&&) = delete; + FileNameHasher& operator=(const FileNameHasher&) = delete; + FileNameHasher& operator=(FileNameHasher&&) = delete; + }; + + + //Cxdec插件储存管理 size = 0x60 + //CompoundStorageMedia : public iTVPStorageMedia + struct CompoundStorageMedia + { + void* VptrTable; //0x00 + int RefCount; //0x04 + DWORD field_8; //0x08 + tTJSString PreFix; //0x0C 资源前缀 + tTJSString HasherSeed; //0x10 Hash盐 + CRITICAL_SECTION CriticalSection; //0x14 临界区锁 + + //0x2C 已加载资源表 + //std::unorder_map ArchiveEntryLoaded + BYTE Reserve[0x20]; + + //0x4C 已加载封包名字 + //std::vector PackageLoaded + tTJSString* Start; + tTJSString* Position; + tTJSString* End; + + //0x58 路径Hash对象 + IStringHasher* PathNameHasher; + + //0x5C 文件名Hash对象 + IStringHasher* FileNameHasher; + }; + + + class HashCore + { + public: + using tCreateCompoundStorageMedia = tjs_error(__cdecl*)(CompoundStorageMedia** retTVPStorageMedia, tTJSVariant* tjsVarPrefix, int argc, void* argv); + + //Hook TVPStorageMedia创建 + friend tjs_error __cdecl HookCreateCompoundStorageMedia(CompoundStorageMedia** retTVPStorageMedia, tTJSVariant* tjsVarPrefix, int argc, void* argv); + //Hook 文件夹路径计算 + friend tjs_int __fastcall HookPathNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed); + //Hook 文件名计算 + friend tjs_int __fastcall HookFileNameHasherCalcute(IStringHasher* thisObj, void* unusedEdx, tTJSVariant* hashValueRet, const tTJSString* str, const tTJSString* seed); + + private: + static constexpr const wchar_t FolderName[] = L"StringHashDumper_Output"; //字符串Dump文件夹 + static constexpr const wchar_t DirectoryHashFileName[] = L"DirectoryHash.log"; //Dump路径日志文件名 + static constexpr const wchar_t FileNameHashFileName[] = L"FileNameHash.log"; //Dump文件名日志文件名 + static constexpr const wchar_t UniversalFileName[] = L"Universal.log"; //通用日志文件名 + + private: + static constexpr const char CreateCompoundStorageMediaSignature[] = "\x55\x8B\xEC\x6A\xFF\x68\x2A\x2A\x2A\x2A\x64\xA1\x00\x00\x00\x00\x50\x83\xEC\x08\x56\xA1\x2A\x2A\x2A\x2A\x33\xC5\x50\x8D\x45\xF4\x64\xA3\x00\x00\x00\x00\xA1\x2A\x2A\x2A\x2A\x85\xC0\x75\x12\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x83\xC4\x04\xA3\x2A\x2A\x2A\x2A\x8B\x75\x0C\x56\xFF\xD0\x83\xF8\x02\x74\x2A\xB8\x15\xFC\xFF\xFF\x8B\x4D\xF4\x64\x89\x0D\x00\x00\x00\x00\x59\x5E\x8B\xE5\x5D\xC3"; + static constexpr const wchar_t Split[] = L"##YSig##"; //输出分隔符 + + std::wstring mDumperDirectoryPath; //Dump输出目录 + Log::Logger mDirectoryHashLogger; //文件夹Hash日志 + Log::Logger mFileNameHashLogger; //文件名Hash日志 + Log::Logger mUniversalLogger; //通用日志 + + private: + HashCore(); + HashCore(const HashCore&) = delete; + HashCore(HashCore&&) = delete; + HashCore& operator=(const HashCore&) = delete; + HashCore& operator=(HashCore&&) = delete; + ~HashCore(); + + public: + + /// + /// 设置Hash表输出路径 + /// + /// 文件夹绝对路径 + void SetOutputDirectory(const std::wstring& directory); + + /// + /// 初始化 (特征码找接口) + /// + /// 代码起始地址 + /// 代码大小 + void Initialize(PVOID codeVa, DWORD codeSize); + /// + /// 检查是否已经初始化 + /// + /// True已初始化 False未初始化 + bool IsInitialized(); + + + /// + /// 获取对象实例 + /// + static HashCore* GetInstance(); + /// + /// 释放 + /// + static void Release(); + }; +} + + diff --git a/CxdecStringDumper/dllmain.cpp b/CxdecStringDumper/dllmain.cpp new file mode 100644 index 0000000..9d36b07 --- /dev/null +++ b/CxdecStringDumper/dllmain.cpp @@ -0,0 +1,28 @@ +#include "Application.h" + +#pragma comment(linker, "/MERGE:\".detourd=.data\"") +#pragma comment(linker, "/MERGE:\".detourc=.rdata\"") + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + UNREFERENCED_PARAMETER(lpReserved); + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + { + Engine::Application::Initialize(hModule); + break; + } + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + { + Engine::Application::Release(); + break; + } + } + return TRUE; +} + +extern "C" __declspec(dllexport) void Dummy() {} \ No newline at end of file diff --git a/KrkrZCxdecV2.sln b/KrkrZCxdecV2.sln index d5df4e0..36b05ee 100644 --- a/KrkrZCxdecV2.sln +++ b/KrkrZCxdecV2.sln @@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxdecExtractorLoader", "Cxd EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxdecExtractorUI", "CxdecExtractorUI\CxdecExtractorUI.vcxproj", "{313D8951-B8B6-482A-86BC-8C4BE0202D97}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxdecStringDumper", "CxdecStringDumper\CxdecStringDumper.vcxproj", "{3ADCCE05-606F-4818-833D-E0E78230E8B0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -27,6 +29,10 @@ Global {313D8951-B8B6-482A-86BC-8C4BE0202D97}.Debug|x86.Build.0 = Debug|Win32 {313D8951-B8B6-482A-86BC-8C4BE0202D97}.Release|x86.ActiveCfg = Release|Win32 {313D8951-B8B6-482A-86BC-8C4BE0202D97}.Release|x86.Build.0 = Release|Win32 + {3ADCCE05-606F-4818-833D-E0E78230E8B0}.Debug|x86.ActiveCfg = Debug|Win32 + {3ADCCE05-606F-4818-833D-E0E78230E8B0}.Debug|x86.Build.0 = Debug|Win32 + {3ADCCE05-606F-4818-833D-E0E78230E8B0}.Release|x86.ActiveCfg = Release|Win32 + {3ADCCE05-606F-4818-833D-E0E78230E8B0}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 7d7f557..3315d52 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# KrkrExtractV2 (ForCxdecV2) 动态解包工具 -  适用于Wamsoft(Hxv4 2021.11+)加密解包 +# KrkrExtractV2 (ForCxdecV2) 动态工具集 +  适用于Wamsoft(Hxv4 2021.11+)加密解包/字符串<->Hash提取 ## 环境   系统: Windows 7 SP1 x64 @@ -11,31 +11,45 @@ ## 与老版本区别   1.重构代码, 稍微看着没那么屎山 -  2.功能拆分, 此项目仅支持解包, 无其他功能 +  2.功能模块拆分为不同Dll   3.修复许多bug ## 如何使用 -  1. `CxdecExtractorLoader.exe`, `CxdecExtractor.dll`, `CxdecExtractorUI.dll`保持同一目录 +  1. `CxdecExtractorLoader.exe`, `CxdecExtractor.dll`, `CxdecExtractorUI.dll`, `CxdecStringDumper.dll`保持同一目录   2. 保证你的游戏是Wamsoft KrkrZ Hxv4加密类型且加密认证已移除 -  3. 拖拽游戏exe到`CxdecExtractorLoader.exe`启动, 弹出解包响应框, 拖拽`xxx.xp3`到响应框内解包 +  3. 拖拽游戏exe到`CxdecExtractorLoader.exe`启动, 弹出模块选择对话框 -  4. 工具不会申请管理员权限进行弹出UAC提权, 游戏与工具务必不要放在C盘 +  4. 选择`加载解包模块`, 弹出解包对话框, 拖拽`xxx.xp3`到框内解包 -  5. 提取后游戏资源位于`游戏目录\Extractor_Output\`文件夹, 包含`xxx文件夹`的封包资源与`xxx.alst`的文件表 +   4.1 `游戏目录\Extractor_Output\`为输出目录, 包含`xxx文件夹`的封包资源与`xxx.alst`的文件表 -  6. 提取日志信息为`工具目录\Extractor.log`文件 +   4.2 `工具目录\Extractor.log`为日志信息 -  7. 如出现错误标题的弹窗报错, 请检查上述步骤 +  5. 选择`加载字符串Hash提取模块`, 自动提取游戏运行时的字符串Hash映射表 + +   5.1 `游戏目录\StringHashDumper_Output\`为输出目录 + +   5.2 `DirectoryHash.log`为文件夹路径Hash映射表 + +   5.3 `FileNameHash.log`为文件名Hash映射表 + +   5.4 `Universal.log`为通用信息(Hash加密参数) + +  6. 选择`加载Key提取模块`(功能暂未实现) + +  7. 工具不会申请管理员权限进行弹出UAC提权, 游戏与工具务必不要放在C盘 + +  8. 如出现错误标题的弹窗报错, 请检查上述步骤 ## 常见问题   Q: 为什么没有资源文件名   A: 封包里面本来就没有文件名 -  Q: 解包响应框支持批量拖拽解包吗 +  Q: 解包对话框支持批量拖拽解包吗   A: 不支持, 仅支持单个封包逐个拖拽提取 @@ -43,14 +57,18 @@   A: 没做多线程支持, 等它慢慢解完就好 +  Q: Hash映射表能一次性提取所有吗 + +  A: 不能, 名字在脚本里面散落到处都是, 且不全 +   Q: 兼容Win7以外的系统吗   A: 理论上兼容, 不过没有测试, 有问题我也不知道 ## 同类工具推荐 - * KrkrExtractV2 (ForCxdecV2)(本工具)   类型: 动态   解包: 一次性  文件名: 无 + * KrkrExtractV2 (ForCxdecV2)(本工具)  类型: 动态   解包: 一次性  文件名: 运行时 - * [KrkrDump](https://github.com/crskycode/KrkrDump)   类型: 动态   解包: 运行时  文件名: 有 + * [KrkrDump](https://github.com/crskycode/KrkrDump)  类型: 动态   解包: 运行时  文件名: 运行时 - * [GARBro](https://github.com/crskycode/GARbro)   类型: 静态(需人工装填)   解包: 一次性  文件名: 无 \ No newline at end of file + * [GARBro](https://github.com/crskycode/GARbro)  类型: 静态(需人工装填)   解包: 一次性  文件名: 无 \ No newline at end of file